What are .mpy modules?
Besides statically linked modules, Micropython also offers a way to dynamically load binary code. These are .mpy files, which can be copied to a device and imported just like an ordinary python module. This has a huge usability benefit over the "normal" binary modules, since users can simply install those on prebuilt Micropython installations. "Installing" a statically linked module requires to (re-)build Micropython yourself.
So I thought having this driver as a dynamically loadable module would be neat.
Limitations of dynamically loaded modules
A dynamically loaded module has no direct access to the function provided by the platform / operating system so only a very limited table of functions is available which are defined in py/nativeglue.h. Normally, this would mean implementing a hardware driver with this system is a bad idea because all the HAL functions are unavailable. But since my driver comes with its own variant of the I2S driver anyway and otherwise does very little hardware specific, I wanted to give it a try and started planning.
Non-IDF version of the I2S driver
For this to work, we need to make a version of the parallel I2S driver, that does not rely on the ESP32 IDF. Looking at the driver, there are four categories interaction with the IDF:
- Direct, memory-mapped hardware access
We need to define the memory addresses of the peripherals. For this we can either use the original symbol tables or we define them ourself based on the defines in soc.h.
- Interrupt allocation
My implementation don't use the I2S interrupt, so we can ignore / remove interrupt related stuff.
- GPIO setup
The required GPIO functions from the IDF are mostly trivial or reside in the ROM anyway, so its easy to port the required functions.
- Peripheral clock gating / reset
In theory, this is trivial, but it requires access to the DPORT registers and that's where the trouble lies...
DPORT mutual access mitigation
The DPORT registers hold bits, to enable / disable and reset peripherals of the CPU. Since these registers are shared between both cores, an access mitigation is required in order to implement bit set / clear operations safely. Sadly, that is something I cannot do from within the dynamically loaded module as the required functions are not accessible. On a single core CPU, I could get away with disabling interrupts while accessing the registers. But since this is a dual-core, things are way more difficult.
This leaves me with only two options:
- Don't use mutual access mitigation and risk adverse side effects.
Chances of this causing trouble are small, but its a terrible way of thinking...
- Give up
.mpy modules are not intended to be used as hardware drivers anyway. It would have been a nice solution though.
For now I decided to not implement my driver as a .mpy module. But I'm still searching for solutions.