I'm working with a PIC32 and MCP2515 SPI based CAN controller and trying to come up with an elegant way of dealing with the asynchronous nature of CAN messages that require multi-byte SPI transactions.
The scenario is that the application is in the middle of sending a string of bytes representing a CAN message through the SPI hardware to the MCP2515.
On PIC18's and non-RTOS environments I disable the MCP interrupt and use the SPI interrupt to transfer the data or even just sit inside the interrupt routine polling and sending bytes.
With FreeRTOS a CANBus task will determine when to send a message. Usually on a timer event or some other event. The message will get placed onto a Q for the SPI task which is pending on the Q waiting for a message. So far easy.
The SPI task selects the correct SPI device and sets up a Tx Q with the string of bytes to transmit. After that the SPI TxBuffer Empty interrupt will empty out the Tx Q and flag a semaphore that the message is done. The SPI task is waiting on that semaphore and once it's received it will look for another message.
However it's possible the MCP2515 can assert a hardware interrupt while the SPI task is in the process of starting to send or sending a message. That interrupt could be for bus errors, transmit message complete or a message has arrived.
Inside the interrupt routine I'm faced with the same issue. I can't just look at a memory location to determine the cause of the interrupt. I have to send a couple of bytes to read the status register. Ideally that should also occur under interrupt and once the second byte has been received pass that to the original MCP2515 interrupt handler.
Confused? That's just the status byte. It could also be that the MCP2515 interrupt handler has request a read of 13 bytes for a full CAN message. Or it has to send a couple of bytes to disable further transmit empty interrupts.
SPI clock rate can be up to 10Mhz but at the moment I'm running 2Mbps so each byte requires 4 uS. It seems the simple solution is to forget there's an RTOS and prevent the MCP2515 interrupts while sending to the device.
The best solution really depends on what the rest of your application is doing. Sending all the data in the interrupt with interrupts disabled would make your system very unresponsive - but maybe in your application that doesn't matter.
You could prevent the MCP interrupt interrupting a CAN interrupt by having its priority below the priority of the CAN interrupt. That would not prevent it asserting an interrupt part way through a transmission though.
Maybe you could just disable the MCP interrupt while a transmission was in progress, then re-enable it afterwards. If that is a solution though, then it would also seem that you could just complete a transmission even if the MCP interrupt had signaled an error half way through. That would prevent you having to have logic to abort a transmission if the MCP interrupt occurred half way though. In that scenario service the MCP interrupt, but have the ISR do nothing other than set a flag to say the MCP is waiting for attention - then when the transmission is complete, if the flag is set, read the MCP status registers to see what it wants.
There are essentially two devices that each need exclusive access which suggests semaphores.
1. SPI channel that supports several distinct devices.
2. MCP2515 CAN modules that use the SPI. (There could be more than one of these)
3. Possibly also EEROM but for now I'm using I2C EEROM to avoid that.
To start with I think I'll use polled transfer for the SPI.
The MCP receive interrupt routine for transferring can, as you suggest just post to a semaphore for a standard, although higher priority, Receive CAN message task that is parked on the same semaphore. That allows it to then request the SPI channel which it will only get once an MCP transmits are complete. Once it has the SPI channel it can transfer data from the MCP2510 to determine the cause of the interrupt.
Copyright (C) Amazon Web Services, Inc. or its affiliates. All rights reserved.