First, the configuration I have is this:
ChibiOS 18.1.2
Nordic NRF52840 MCU
ARM Cortex-M4 core
1MB flash
256KB RAM
NVIC implements 3 priority bits (8 priority levels total)
Peripherals: UART0, TIMER1, TIMER2, SPI0, SPI3, QSPI0, GPIO, I2C0, I2S0, SysTick timer
Additional support: Nordic S140 SoftDevice
The SoftDevice is basically a binary blob from Nordic which implements driver support for the on-board BlueTooth radio and a BLE5 stack. Normally when you load ChibiOS onto an ARM target, it's positioned at address 0 in the flash. This is also where the interrupt vector table lives, which is constructed as part of the ChibiOS build process. So normally ChibiOS' interrupt handlers service interrupts directly.
However when you use the SoftDevice, things are a bit different:
- The SoftDevice is flashed at address 0
- The SoftDevice provides its own interrupt vector table
- The "application" (in this case ChibiOS) is placed at different flash offset, immediately after the SoftDevice blob (in this case 0x26000)
- The SoftDevice acts as a filter, capturing interrupt calls and forwarding them to the application
- Software makes calls to the SoftDevice using the SVCall instruction
- When active, the SoftDevice notifies the application of events using a software interrupt (there are four SWI vectors in the NRF52840 vector table)
Initially the SoftDevice is not activated, in which case it just passes through all interrupt calls to the application code. The only effect is a slight increase in interrupt latency. When it's turned on, it takes over control of certain peripherals and interrupts (notably the radio) and uses SWI interrupts to signal radio-related events.
Because the SoftDevice uses the SVCall vector, and because it wants to intercept/filter all interrupts, I had to do to the following:
#define CORTEX_SIMPLIFIED_PRIORITY TRUE
#define CRT0_VTOR_INIT FALSE
I rigged up my linker script to place the ChibiOS vector table at 0x26000 and fixed it so that I can link ChibiOS and the SoftDevice into a sigle image, and I can now flash the whole thing in one go and it all works. Everything seemed fine, until I turned on the asserts and checks and ran into the priority order violation.
Also, I increased the system tick frequency like this:
#define CH_CFG_ST_FREQUENCY 24576
I checked carefully to make sure that I wasn't calling an I-class function without a reschedule. That was not the case. It seemed as if the problem only manifested when I enabled the SoftDevice. I most often got it trigger when there was heavy I/O activity. In this case, I was trying to display video and play sound, which involves:
- SPI0 (reading data from SD card, polling touch screen)
- SPI3 (writing to the display)
- I2S (writing audio samples)
In the meantime, the SoftDevice gets radio events and signals them to a separate thread. This thread makes an SVCall to pop events off the event queue. There may be other SVCalls made to manage other things.
Ultimately I tracked the problem down to interrupt priorities. Originally things were set like this:
#define CORTEX_MAX_KERNEL_PRIORITY 0U
#define CORTEX_PRIORITY_PENDSV CORTEX_MAX_KERNEL_PRIORITY
#define NRF5_ST_PRIORITY CORTEX_MAX_KERNEL_PRIORITY
The latter is the SysTick timer priority. Also, all peripherals were set to either 2, 3 or 5.
What ultimately seemed to fix the problem was to set all the priorities like this:
#define NRF5_SPI_SPI0_IRQ_PRIORITY 5
#define NRF5_SPI_SPI3_IRQ_PRIORITY 5
#define NRF5_SPI_QSPI0_IRQ_PRIORITY 5
#define NRF5_SERIAL_UART0_PRIORITY 5
#define NRF5_GPT_TIMER1_IRQ_PRIORITY 5
#define NRF5_GPT_TIMER2_IRQ_PRIORITY 5
#define NRF5_I2C_I2C1_IRQ_PRIORITY 5
#define NRF5_EXT_GPIOTE_IRQ_PRIORITY 5
#define NRF5_SD_IRQ_PRIORITY 5 /* SoftDevice event interrupt via SWI */
#define NRF5_I2S_IRQ_PRIORITY 5
#define NRF5_ST_PRIORITY 5
The PendSV priority is still set to CORTEX_MAX_KERNEL_PRIORITY though.
One other data point: as an experiment last night I set things like this:
#define NRF5_SPI_SPI0_IRQ_PRIORITY 3
#define NRF5_SPI_SPI3_IRQ_PRIORITY 3
#define NRF5_SPI_QSPI0_IRQ_PRIORITY 3
#define NRF5_SERIAL_UART0_PRIORITY 3
#define NRF5_GPT_TIMER1_IRQ_PRIORITY 3
#define NRF5_GPT_TIMER2_IRQ_PRIORITY 3
#define NRF5_I2C_I2C1_IRQ_PRIORITY 3
#define NRF5_EXT_GPIOTE_IRQ_PRIORITY 3
#define NRF5_SD_IRQ_PRIORITY 3
#define NRF5_I2S_IRQ_PRIORITY 3
#define NRF5_ST_PRIORITY 4
and I left the board running. When I checked this morning, it had hit a priority order violation assert again.
Anyway, while I'm pretty sure that the SysTick priority is the culprit here, I don't fully understand why. If someone can clarify it for me, I would appreciate it. (Note that since I think I have things working ok now, I'm not in a huge rush for an answer or anything; I just don't like it when things work and I can't explain why.
-Bill