Raspberry Pico, Alarms and SPI

Apologies if I have the incorrect forum or topic.

I am running the Arduino IDE with Arduino with Arduino Mbed OS RP2040 support for the Raspberry Pi installed.

I am using Alarms to provide a timed interrupt as per this blog ( Raspberry Pi Pico, Arduino Core and Timers – Kevin's Blog ). That all works fine and I can toggle a digital out to get a nice fast square wave.

However I am trying to use SPI to drive a MCP4922 DAC with the following code (and a SPI.begin() in the setup:

‘‘int DAC = 8;
void mcpWrite(int value, int Channel) {
//set top 4 bits of value integer to data variable
byte data = value >> 8;
data = data & 0b00001111;
if (Channel == 0)
data = data | 0b00110000; //DACA Bit 15 Low
else
data = data | 0b10110000; //DACB Bit 15 High
digitalWrite(DAC, LOW); //CS
SPI.beginTransaction(SPISettings(20000000, MSBFIRST, SPI_MODE0));
SPI.transfer(data);
data = value;
SPI.transfer(data);
SPI.endTransaction();
digitalWrite(DAC, HIGH);
}’’

Now, if I put this in the loop it works fine. However, if I put this in the interrupt routine, the code fails and the system hangs ( the inbuilt LED flashes a pattern oddly). Is there a reason SPI and Alarms don’t function nicely together? Hopefully, someone has an idea .

Regards
Andy

Full code here:

#include <hardware/timer.h>
#include <hardware/irq.h>
#include “SPI.h”

#define ALARM_NUM 1
#define ALARM_IRQ TIMER_IRQ_1

#define ALARM_FREQ 1250
uint32_t alarmPeriod;

int DAC = 8;
void mcpWrite(int value, int Channel) {
//set top 4 bits of value integer to data variable
byte data = value >> 8;
data = data & 0b00001111;
if (Channel == 0)
data = data | 0b00110000; //DACA Bit 15 Low
else
data = data | 0b10110000; //DACB Bit 15 High
digitalWrite(DAC, LOW); //CS
SPI.beginTransaction(SPISettings(20000000, MSBFIRST, SPI_MODE0));
SPI.transfer(data);
data = value;
SPI.transfer(data);
SPI.endTransaction();
digitalWrite(DAC, HIGH);
}

const float Pi = 3.14159;
const float twoPi = 2.0 * Pi;
float theta = 0.0;
float delta = twoPi / 100;
float Val = 0.0;

int ledval;
int WaveOut = 0;
uint32_t MaxCount = 10;
uint32_t Count = 0;
static void alarm_irq(void) {

ledval = !ledval;
digitalWrite(7, ledval);
if (Count == 0) {
WaveOut = !WaveOut;
digitalWrite(9, WaveOut);
Val = 0.5 * sin(theta) + 0.5;
mcpWrite(4095*Val , 0);
theta = theta + delta;
if (theta > twoPi)
theta = 0;
}
Count++;
if (Count == MaxCount)
Count = 0;

hw_clear_bits(&timer_hw->intr, 1u << ALARM_NUM);
alarm_in_us_arm(alarmPeriod);

}

static void alarm_in_us_arm(uint32_t delay_us) {
uint64_t target = timer_hw->timerawl + delay_us;
timer_hw->alarm[ALARM_NUM] = (uint32_t) target;
}

static void alarm_in_us(uint32_t delay_us) {
hw_set_bits(&timer_hw->inte, 1u << ALARM_NUM);
irq_set_exclusive_handler(ALARM_IRQ, alarm_irq);
irq_set_enabled(ALARM_IRQ, true);
alarm_in_us_arm(delay_us);
}

void setup () {
pinMode(DAC, OUTPUT);
pinMode (7, OUTPUT);
pinMode (9, OUTPUT);
pinMode (LED_BUILTIN, OUTPUT);
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
SPI.begin();
Serial.println(MOSI);
Serial.println(MISO);
Serial.println(SCK);
Serial.end();
alarmPeriod = 1000000 / ALARM_FREQ;
alarm_in_us(alarmPeriod);
mcpWrite(4095, 0);

}

int ledval2;
void loop () {
ledval2 = !ledval2;
digitalWrite(LED_BUILTIN, ledval2);

}

Ahoj,

please be so kind and use ``` before and after your code for better formatting in your post.

MbedOS has all methods like read, write, etc covered with Mutex and mutex can not be used in interrupt context.
So you probably need to change it to another solution. Maybe set a flag in interrupt context, then process it in the main loop and clear the flag.

BR, Jan

Thanks Johnny.

I’m afraid I’ve not used this system so was unaware of the ``` code. I did look for a way to do it before posting. Thanks for the tip.

Hmm, I’ll have to have a look at this. I’ve been using a STM32 (with Arduino tooling) and that seems to manage fine with this sort of code. Thanks for the pointer though.

Andy

Actually @JohnnyK I realised there is a misunderstanding. I’m talking about AuduinoCore-Mbed:

not MbedOs. ArduinioCore is not a RTOS. I may be asking in the wrong place ?

Of course if your issue is about the Arduino, then visit Arduino forum.

I do not know how it is exactly done, but the MbedOS include RTOS and ArduinoCore-mbed include MbedOS, so if it not use the bare metal profile then it uses also RTOS.

if I put this in the interrupt routine, the code fails and the system hangs ( the inbuilt LED flashes a pattern oddly).

This behavior seems to be exactly what I descripted above, of course maybe I am wrong. If not then it is MbedOS crash and you can found an error report printed from a default serial output.

BR, Jan

I think Jan is right. The SPI::write() method was protected by a mutex already in Mbed 2, with no RTOS present, as you can see below:

int SPI::write(int value)
{
    lock();
    _acquire();
    int ret = spi_master_write(&_spi, value);
    unlock();
    return ret;
}
...
void SPI::lock()
{
    _mutex->lock();
}

void SPI::unlock()
{
    _mutex->unlock();
}

The reason for this is that the SPI’s write function is not re-entrant. Imagine that your SPI master has two SPI slaves to write to. The first one is done in a loop (as in your first attempt) and the second one in a Ticker’s interrupt service routine (as in your second attempt). If the Ticker’s interrupt happens to occur when the SPI master is in the middle of writing to the first slave then the write function is interrupted and called again from the interrupt service routine causing a system crash. That’s why the Mbed designers decided to protect (lock) it with a mutex as Jan said and it must not be called from an ISR (interrupt service routine).

Thanks @hudakz That’s great information and gives me something to work with ! I might need to drop down to spi_master_write and do the protection myself. FYI I do have a similar code working fine (and driving a OLED and DAC ) for a STM32.

That sounds like a reasonable solution. Especially when there is only one slave to write to. By the way, you can expect similar troubles with the I2C too.