访问block设备的文件 和随机访问大数据是两回事. IDF和Micropython又是两回事.
你考虑一个有个文件 比RAM还大 没有编在固件里 没有给编译器RO段信息 即使在IDF里面 运行时才想去随机访问它的某个32位数 也是不可能的 除了用文件打开 不停seek
但是做了内存映射 就可以直接使用指针 地址之类 直接访问了 效率不一样
代码管在XXXXXXXXXXXXXXXXXX/m24h/esp32flash_mpy
最近在操作一个ESP32C3的开发板 才知道什么叫做憨牛充栋 还有什么叫做捉襟见肘
ESP32C3大抵不是为了Micropython设计的 或许使用IDF 它的内存足够一般应用了
但是如果先塞进去一个Micropython 情况就不一样了 一个原本具备数百k大小内存的MCU 到最后 我必须1k 1k地抠字节 才能勉强启动基本支撑框架 包括蓝牙 无线 OLED显示 网页服务等等 我曾经提了个bug给micropython 只申请2个20k内存 然后激活蓝牙 系统就崩溃了
再加上Micropython本身又内存碎片的问题 动态申请不同大小的内存不可避免 现在不崩溃 以后也得崩溃 除非留出足够的内存余量 这就得在螺蛳壳里做道场了
于是我遇到了一个问题 就是汉字字库 光是用于二分搜索法的汉字索引 就有20k大小 在IDF中 基本不用考虑 嵌入固件中就是 但是这里 就是一个不能接受的内存大户
因为Micropython需要将用到的外部资源 都加载到RAM中 我寻找网上的解决方案 目前没有一个可用的方法 能够直接将资源放在flash中使用 除非自己编译固件 如果是那样 我还不如直接用IDF 毕竟它更容易从github递归克隆 而且 编译Micropython也需要递归克隆 我感觉就跟偷运两只大象一样困难
使用文件来访问是一种方法 但是对于随机读取数个字 作为块设备的文件 其实不合适的 也不是不能 但是效率可能会令人难以接受
但是花了一天时间 总算找到了将资源放到flash 直接使用的方法
首先 先得从flash上划出地盘来 这就需要在载入Micropython 没有运行前就进行 就是修改分区表 划分出单独一个分区 这个需要在首次运行前进行的原因 是因为Micropython会在首次运行时寻找第一个数据区进行格式化
原本的分区表 给数据划分了2M空间 我从中拿出1M用来存放静态资源 就变成
data, nvs, 0x9000, 0x6000, nvs, data, phy, 0xf000, 0x1000, phy_init, app, factory, 0x10000, 0x1f0000, factory, data, fat, 0x200000, 0x100000, vfs, data, undefined, 0x300000, 0x100000, bin,
网上的分区表工具太繁琐 我自己写了一个 将以上CSV文件转换成可用的可载入的二进制码
import sys from hashlib import md5 from struct import pack if len(sys.argv)<3: print (f'''\ Create binary parition table from .csv file. Usage: python {sys.argv[0]} <.csv filename> <.bin filename> ''') exit(1) types={ 'app':0, 'data':1 } subtypes={ 0:{ 'factory':0, 'test':0x20, }, 1:{ 'ota':0, 'phy':1, 'nvs':2, 'coredump':3, 'nvs_keys':4, 'efuse':5, 'undefined':6, 'esphttpd':0x80, 'fat':0x81, 'spiffs':0x82, }, } flags={ 'encrypted':0, } bin=bytearray() with open(sys.argv[1], 'r') as f: while line:=f.readline(): line=line.strip('\r\n \t') if not line or line.startswith('#'): continue csv=line.strip('\r\n \t').split(',') bin.extend(b'\xAA\x50') bin.extend(pack('B', type:=types[csv[0].strip(' \t').lower()])) bin.extend(pack('B', subtypes[type][csv[1].strip(' \t').lower()])) bin.extend(pack('<L', eval(csv[2].strip(' \t')))) bin.extend(pack('<L', eval(csv[3].strip(' \t')))) bin.extend(pack('16s', csv[4].strip(' \t').encode('utf-8'))) flag=0 if len(csv)>5: for t in csv[5].split(':'): if t:=t.strip(' \t'): flag=flag+(1<<flags[t]) bin.extend(pack('<L', flag)) bin.extend(b'\xEB\xEB'+b'\0'*14+md5(bin).digest()) bin.extend(b'\xFF'*(0xc00-len(bin))) with open(sys.argv[2], 'wb') as f: f.write(bin)
然后用"XXXXXXXXXX -p <端口> write_flash 0x8000 <生成的二进制码分区表文件>" 上载即可
下面就是关键了 怎么样像使用内存一样直接访问flash呢 Micropython有machine.mem32等访问内存的方法 但是对ESP32是不能用的 因为ESP32的内存管理有点复杂 它的内存映射和STM32不一样 不是固定的 MCU上的内存地址要转换成FLASH上的地址 有一个叫内存管理单元(MMU)的东西进行管理 我观察了Micropython的大部分代码 都完全没有涉及这块 显然全由bootloader根据app分区运行需要 给它进行内存映射 就这 还不是线性的
解决方法就是 将尚未映射的物理地址 全部都映射上去 幸好ESP的内存映射不像计算机的CPU那么复杂 否则我就可以直接放弃了 还幸好MMU映射表 可以用machine.mem32访问 下面这个模块就是使用非线性映射方式 可以直接访问flash的每个32 16 8位数 ... 嗯 Micropython应用涉及到内存管理核心 我估计也找不到其他的了
需要注意 这个代码只适合ESP32C3 对于其他ESP32 参数需要调整 目前我还没试其他的
# module flash.py import sys import machine from array import array from struct import unpack if 'esp32c3' in sys.implementation._machine.lower(): import esp import esp32 page=4096 size=esp.flash_size() from esp import flash_read as readinto from esp import flash_write as write from esp import flash_erase as erase # make all flash MMU mapped, for ESP32C3, there are 128 entries, each enter means 64k, # and virtual address is from 0x3c000000, MMU table is from 0x600C5000 _mmu=array('L', (0,)*128) free=array('L') for i in range(128): t=machine.mem32[0x600C5000+(i<<2)] if t&0x100: free.append(i) elif not t&~0x7F: _mmu[t]=0x3c000000+(i<<16) t=0 for i in range(size>>16): if not _mmu[i]: if t>=len(free): raise MemoryError('No available MMU entry') machine.mem32[0x600C5000+(free[t]<<2)]=i _mmu[i]=0x3c000000+(free[t]<<16) t=t+1 def vaddr(faddr): return _mmu[faddr>>16]+(faddr&0xffff) # find a partition which has a label 'bin' if t:=esp32.Partition.find(type=esp32.Partition.TYPE_DATA, label='bin'): t=t[0].info() bin_addr=t[2] bin_size=t[3] else: bin_addr=0 bin_size=0 del t, i, free else: raise NotImplementedError('Not a supported MCU') def mem32(pos): return machine.mem32[vaddr(pos)] def mem16(pos): return machine.mem16[vaddr(pos)] def mem8(pos): return machine.mem8[vaddr(pos)] class Bin: def __init__(self, addr, size): self.addr=addr self.size=size def mem32(self, pos): return machine.mem32[vaddr(pos+self.addr)] def mem16(self, pos): return machine.mem16[vaddr(pos+self.addr)] def mem8(self, pos): return machine.mem8[vaddr(pos+self.addr)] def readinto(self, offset, barray): readinto(offset+self.addr, barray) # encoded label must be no longer than 8 bytes def findsub(self, label): idx=0 b=bytearray(16) label=label.encode('utf-8') while idx+16<=self.size: readinto(self.addr+idx, b) idx=idx+16 addr, size, name=unpack('<LL8s', b) if addr==0xFFFFFFFF or addr==0: break if name.strip(b' \0\xFF')==label: return Bin(self.addr+addr, size) return None bin=Bin(bin_addr, bin_size) if bin_size else None
这里我还做了一个Bin类 将分区中的数据 模仿出一个可以分目录的类文件系统 但是名字只有8字节 而且可以随机访问任何32 16 8位数 另外 将现有资源文件打包的脚本也写了
import sys from struct import pack if len(sys.argv)<2: print (f'''\ Pack files into a binaray data for using raw ESP32 partition by my flash.py module. This script will use the file name as data label, which must not be longer than 8 bytes. Usage: python {sys.argv[0]} <packed filename> [files to be packed] ... ''') exit(1) addr=16*(len(sys.argv)-1) bin=bytearray(b'\xFF'*addr) pos=0 for fname in sys.argv[2:]: with open(fname, 'rb') as f: b=f.read() bin.extend(b) t=len(b) bin[pos:pos+16]=pack('<LL8s', addr, t, fname.encode('utf-8')) pos=pos+16 addr=addr+t # align to 16 bytes t=(16-(addr%16))%16 bin.extend(b'\xFF'*t) addr=addr+t with open(sys.argv[1], 'wb') as f: f.write(bin)
使用的步骤是 先将最分枝的文件打包 然后层层往上汇聚打包 (其实我根本就不分第二层了 太累) 然后使用"XXXXXXXXXX -p <端口号> 0x300000 <打成最终的包>"来上载到开始地址为3M的第二数据分区
然后使用之前提供的Bin类就可以直接访问上面的字节了 一个代码例子如
class FontBin(Font): def __init__(self, b): self.cnt=b.mem32(4) super().__init__(b.mem32(8), b.mem32(12)) self.bin=b ... _font_eng5X8=FontBin(flash.bin.findsub('eng5X8'))
这大概是目前唯一在Micropython中直接访问flash字节的方法 虽然Micropython本身有flash读取块的方法 但是远不如做好内存映射 然后直接读取内存地址来得高效
[修改于 7个月7天前 - 2024/04/18 09:26:18]
一位ESP32的Arduino和IDF用户路过我想说的是SPIFFS用mkspiffs工具很容易把一...
访问block设备的文件 和随机访问大数据是两回事. IDF和Micropython又是两回事.
你考虑一个有个文件 比RAM还大 没有编在固件里 没有给编译器RO段信息 即使在IDF里面 运行时才想去随机访问它的某个32位数 也是不可能的 除了用文件打开 不停seek
但是做了内存映射 就可以直接使用指针 地址之类 直接访问了 效率不一样
对于挂载在外部flash的文件,其实从底层角度上看,是通过SPI或者QSPI的形式对数据进行读取的也...
你这样岂不是更复杂 而且操作flash的SPI 可能会和ROM里面程序 甚至和MCU的功能进行冲突 造成不可预见的麻烦
Micropython的访问 仅仅是调用IDF提供的接口 而指针地址访问 全在虚拟地址上
虽然我想说许多 但是最终还是说 你想简单了 不然实现看看 给个代码或者伪码 或者哪个IDF或者Arduino库函数或者功能 能让你直接访问Flash
无意冒犯,不要上火,技术讨论,还是回到技术上把🤣任何编程工具都有其存在的价值,Micropytho...
你压根就没有理解 我做的东西 具体实现了什么能力
首先 无论是esp_flash_xx()还是esp_partition_xx micropython都早搬过去了 如果够用 我又何必操作MMU 这些都是块操作 而不是直接地址访问
pgmspace.h的功能 与micropython的machine.memXX() 一样 都是访问虚地址 而不是真实地址 对于AVR或者STM32是可用的 对于esp32是不行的 你连esp32的flash 在什么指针地址都不知道 更不可能知道它是不连续的 是分段操作的
你既然在做调取esp32主flash数据块什么的 你不妨考虑一下 你能否单独读取flash的某个字节 高效地 就好像用指针一样访问 而不是为了某个字节 就调用一次什么函数读取一大块 (或者最多把读取字节数设为1 但是这样就算高效吗) 如果你发现你做不到 你才明白要学什么
正因为我知道你不知道 我又怎么可能上火 或者觉得冒犯呢 看了你的东西 我只是感到白期待了 毕竟我也想知道 有什么我不知道的库函数能够不需要自己操作MMU表 但遗憾的是 看来乐鑫并未提供出来
如果你想深入点esp32 其实这个文章的知识点是很少有人提起 更应该是没有具体其他实现案例的 连说明文档都很难找到的 你根本就不会在官方公布的东西里找到细节 包括MMU表的地址等等 (我也是翻IDF源代码才找到一些痕迹) 可惜了 明珠暗投
深入 请深入 不要停留在肤浅的随便一观上
时段 | 个数 |
---|---|
{{f.startingTime}}点 - {{f.endTime}}点 | {{f.fileCount}} |
200字以内,仅用于支线交流,主线讨论请采用回复功能。