CALLBACK_CRITICAL_BEGIN is heavyweight, but we can be confident we do
not have work to do as long as callback_head is NULL.
This gives back performance on nRF.
Before this, the mp3 file would be read into the in-memory buffer
only when new samples were actually needed. This meant that the time
to read mp3 content always counted against the ~22ms audio buffer length.
Now, when there's at least 1 full disk block of free space in the input
buffer, we can request that the buffer be filled _after_ returning from
audiomp3_mp3file_get_buffer and actually filling the DMA pointers. In
this way, the time taken for reading MP3 data from flash/SD is less
likely to cause an underrun of audio DMA.
The existing calls to fill the inbuf remain, but in most cases during
streaming these become no-ops because the buffer will be over half full.
In time, we should transition interrupt driven background tasks out of the
overall run_background_tasks into distinct background callbacks,
so that the number of checks that occur with each tick is reduced.
The motivation for doing this is so that we can allow
common_hal_mcu_disable_interrupts in IRQ context, something that works
on other ports, but not on nRF with SD enabled. This is because
when SD is enabled, calling sd_softdevice_is_enabled in the context
of an interrupt with priority 2 or 3 causes a HardFault. We have chosen
to give the USB interrupt priority 2 on nRF, the highest priority that
is compatible with SD.
Since at least SoftDevice s130 v2.0.1, sd_nvic_critical_region_enter/exit
have been implemented as inline functions and are safe to call even if
softdevice is not enabled. Reference kindly provided by danh:
https://devzone.nordicsemi.com/f/nordic-q-a/29553/sd_nvic_critical_region_enter-exit-missing-in-s130-v2
Switching to these as the default/only way to enable/disable interrupts
simplifies things, and fixes several problems and potential problems:
* Interrupts at priority 2 or 3 could not call common_hal_mcu_disable_interrupts
because the call to sd_softdevice_is_enabled would HardFault
* Hypothetically, the state of sd_softdevice_is_enabled
could change from the disable to the enable call, meaning the calls
would not match (__disable_irq() could be balanced with
sd_nvic_critical_region_exit).
This also fixes a problem I believe would exist if disable() were called
twice when SD is enabled. There is a single "is_nested_critical_region"
flag, and the second call would set it to 1. Both of the enable()
calls that followed would call critical_region_exit(1), and interrupts
would not properly be reenabled. In the new version of the code,
we use our own nesting_count value to track the intended state, so
now nested disable()s only call critical_region_enter() once, only
updating is_nested_critical_region once; and only the second enable()
call will call critical_region_exit, with the right value of i_n_c_r.
Finally, in port_sleep_until_interrupt, if !sd_enabled, we really do
need to __disable_irq, rather than using the common_hal_mcu routines;
the reason why is documented in a comment.
Not all boards have external flash or other components that make them
require 2.7V -- sometimes we can get considerably longer battery life
by decreasing this requirement.
In particular, pewpew10 and pewpew_m4 are powered directly from
battery, with no LDO, and should work fine down to 1.6V.
I discussed with Hierophect on Discord about how to "de-nest" the code
for configuring SPI objects on STM, because the problems with one
nesting level per pin becomes unmanageable with the up to 10 pins of
SDIO.
This code (which is only compile-tested so far) demonstrates the concept
we discussed.
The SCK pin is always required. Loop over all possibilities of the SCK
pin. When we are considering a particular item in the mcu_spi_sck_list
we have now become committed to using a particular periph_index. If all
the other pins can be satisfied by that periph_index, then we have a
working combination. Once we have a working combination that is not
reserved, we can return that combination. On reaching the end, we have
checked all the possible possibilities and can give the same errors as
before: One if there was a possibility that worked but was reserved;
and another if no possibility worked.