To continue the presentation of the refactoring of the Cosa SPI class here are some further details. Below is a snippet from the new header file.
class SPI {
public:
enum Clock {
DIV4_CLOCK = 0x00,
...
MASTER_CLOCK = 0x08,
DEFAULT_CLOCK = DIV4_CLOCK
} __attribute__((packed));
enum Order {
MSB_ORDER = 0,
LSB_ORDER = 1,
DEFAULT_ORDER = MSB_ORDER
} __attribute__((packed));
class Driver {
protected:
...
public:
Driver(Board::DigitalPin cs,
Clock clock = DEFAULT_CLOCK,
uint8_t mode = 0,
Order order = MSB_ORDER,
Interrupt::Handler* irq = 0);
};
public:
SPI();
bool begin(Driver* dev);
bool end();
uint8_t transfer(uint8_t data);
void transfer(void* buffer, uint8_t count);
...
};
The most important addition to the previous SPI interface is that the SPI::Device class, which is the device driver support class, holds information about the device SPI hardware setting, chip select and possible interrupt handler (pin).
SPI::begin() and SPI::end() become essential functions that mark the beginning and end of an SPI transaction. They will handle 1) asserting the chip select pin, 2) disabling/enabling all interrupt sources on the SPI bus (if any) and most important 3) setting the SPI hardware state for each transaction. This allows several devices with different SPI settings to be integrated without conflicts.
Below is a snippet from the current implementation. NB: The macro synchronized marks a block of code as mutual exclusive (i.e. interrupt handing is turned off). The macro is part of the syntax abstraction in Cosa (See Cosa/Types.h).
bool
SPI::begin(Driver* dev)
{
synchronized {
...
m_dev = dev;
// Initiate the SPI hardware with the device driver settings (context)
SPCR = dev->m_spcr;
SPSR = dev->m_spsr;
// Select the device
dev->m_cs.toggle();
// Disable interrupts from SPI devices
for (dev = spi.m_list; dev != 0; dev = dev->m_next)
if (dev->m_irq) dev->m_irq->disable();
}
return (true);
}
bool
SPI::end()
{
synchronized {
...
// Deselect the device
m_dev->m_cs.toggle();
// Enable interrupts from SPI devices
for (Driver* dev = spi.m_list; dev != 0; dev = dev->m_next)
if (dev->m_irq != 0) dev->m_irq->enable();
// Release the SPI driver support
m_dev = 0;
}
return (true);
}
Below is a snippet from the ST7735 TFT device driver to gives an idea of typical usage of the SPI device driver support. NB: The asserted(pin) is a macro that will toggle the pin before and after the block. The chip select is handled by SPI::begin/end. Please also note that the internal/protected write methods are used within a transaction as set_port() and draw_pixel(). These two Canvas methods are given in full detail to give an idea how this works.
class ST7735 : public Canvas, SPI::Driver {
protected:
OutputPin m_dc;
enum Command {
NOP = 0x0, // No Operation
RDDID = 0x04, // Read Display ID
RDDST = 0x09, // Read Display Status
RDDPM = 0x0A, // Read Display Power Mode
...
} __attribute__((packed));
...
void write(Command cmd)
{
asserted(m_dc) {
spi.transfer(cmd);
}
}
void write(Command cmd, uint8_t data)
{
asserted(m_dc) {
spi.transfer(cmd);
}
spi.transfer(data);
}
void write(Command cmd, uint16_t data)
{
asserted(m_dc) {
spi.transfer(cmd);
}
spi.transfer(data >> 8);
spi.transfer(data);
}
void write(Command cmd, uint16_t x, uint16_t y)
{
asserted(m_dc) {
spi.transfer(cmd);
}
spi.transfer(x >> 8);
spi.transfer(x);
spi.transfer(y >> 8);
spi.transfer(y);
}
public:
ST7735(Board::DigitalPin cs = Board::D10,
Board::DigitalPin dc = Board::D9);
void set_port(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1)
{
spi.begin(this);
write(CASET, x0, x1);
write(RASET, y0, y1);
write(RAMWR);
spi.end();
}
virtual void draw_pixel(uint8_t x, uint8_t y)
{
set_port(x, y, x + 1, y + 1);
color16_t color = get_pen_color();
spi.begin(this);
spi.transfer(color.rgb >> 8);
spi.transfer(color.rgb);
spi.end();
}
...
};
Cheers!