Many SPI devices require small transfers, somewhere between one and <not many> bytes. These short transfers must be enclosed in a pair of SPI.beginTransaction() and SPI.endTransaction() calls.
Sometimes it's necessary to read e.g. a single status byte from a device to decide if further transfers are necessary; something along these lines:
It's not really hard to create matching pairs of those, but it's also not hard to screw that up. That's why I have created an SPI_scopedTransaction macro:
explanation below, let's first see how it makes writing SPI code easier:
So how does it work?
A for-loop has the following structure:
What happens?
The helper also has a member done_ which is properly initialized to false in the constructor as well. We need that to track the number of executions of statement, which should be exactly one. As soon as increment is executed, done_ is set to true. The next check of condition will fail. Great - the loop is executed only once.
Now we also need to make sure that SPI.endTransaction() is called after statement was executed, no matter how and when the loop is ended (either because it finished or because return was called, there are a few ways). We cannot rely on increment for that if there is an intermediate return. What we can rely on, however, is that all destructors are called when the loop is finished - so SPI.endTransaction() is in the helper's destructor.
The ATOMIC_BLOCK macro inspired me here, but I needed this stack exchange answer for the anonymous struct thing.
A pull request for Paul is underway.
Sometimes it's necessary to read e.g. a single status byte from a device to decide if further transfers are necessary; something along these lines:
Code:
bool readFromDeviceIfReady(outValue& result)
{
SPI.beginTransaction(deviceSettings);
if (SPI.readByte(0) != device_indicates_readiness)
{
SPI.endTransaction();
return false;
}
uint8_t buffer[numberOfBytes];
SPI.transfer(someBuffer, numberOfBytes);
result = convertToResult(buffer, numberOfBytes);
SPI.endTransaction();
return true;
}
Code:
#define SPI_scopedTransaction(settings) \
for( \
struct \
{ \
struct Helper \
{ \
Helper() : done_(false) \
{ \
SPI.beginTransaction(settings); \
} \
~Helper() \
{ \
SPI.endTransaction(); \
} \
bool done_; \
}; \
\
bool done() const \
{ \
return helper_.done_; \
} \
void exec() \
{ \
helper_.done_ = true; \
} \
Helper helper_; \
} scope; !scope.done() ; scope.exec())
Code:
bool readFromDevice(uint32_t& result)
{
SPI_scopedTransaction(SPISettings())
{
if (SPI.transfer(0) != 1) // let's assume 1 indicates ready
{
return false;
}
uint32_t buffer;
SPI.transfer(&buffer, sizeof(buffer));
result = buffer;
}
return true;
}
So how does it work?
A for-loop has the following structure:
Code:
for (initialization; condition; increment) statement
- The initialization expression is executed. It can declare a counter variable (or several of the same type) and can also initialize it. The variable type can basically be anything, including an anonymous struct. Such an anonymous struct is declared by the macro. Unfortunately, since it is anonymous, it cannot have non-default constructor and destructor. We'll get back to that later.
- If condition evaluates to true, statement is executed (that can also be a block {}). Otherwise, the loop proceeds at 4.
- increment is executed and the loop proceeds at 2.
- the loop ends.
The helper also has a member done_ which is properly initialized to false in the constructor as well. We need that to track the number of executions of statement, which should be exactly one. As soon as increment is executed, done_ is set to true. The next check of condition will fail. Great - the loop is executed only once.
Now we also need to make sure that SPI.endTransaction() is called after statement was executed, no matter how and when the loop is ended (either because it finished or because return was called, there are a few ways). We cannot rely on increment for that if there is an intermediate return. What we can rely on, however, is that all destructors are called when the loop is finished - so SPI.endTransaction() is in the helper's destructor.
The ATOMIC_BLOCK macro inspired me here, but I needed this stack exchange answer for the anonymous struct thing.
A pull request for Paul is underway.