A projects to learn the SPI API of the RP2040 C SDK, and to use it to control an external Flash IC. In this post, I refine the m25 c++ class from previous post.
|
What will Change and What Not?
The class initially only knew the SPI device and chip select pins. SPI and pin initialisation was done in main().
// m25.h class m25 { public: m25(spi_inst_t * spi, uint cs) : _spi(spi), _cs(cs) {} // ... } // ... // main file: void setSPI() { spi_init(M25_SPI, 1000 * 1000); gpio_set_function(M25_SPI_RX_PIN, GPIO_FUNC_SPI); gpio_set_function(M25_SPI_SCK_PIN, GPIO_FUNC_SPI); gpio_set_function(M25_SPI_TX_PIN, GPIO_FUNC_SPI); // Chip select is active-low, so we'll initialise it to a driven-high state gpio_init(M25_SPI_CSN_PIN); gpio_put(M25_SPI_CSN_PIN, 1); gpio_set_dir(M25_SPI_CSN_PIN, GPIO_OUT); } int main() { // ... setSPI(); eeprom = new m25(M25_SPI, M25_SPI_CSN_PIN); // ...
Constants used in the c++ class were done with #define.
#define FLASH_CMD_RDID 0x9F
I will not (yet) create a more generic spi class, and derive from that (or use it). I hope that one day some volunteer writes a good c++ library for the RP2040. I will then piggyback on that.
Why and How?
"Why" for a few reasons, and they are all debatable.
The define turns into a private class-scope enum. I've also given it the right type. It belongs to the class, does not have to be used outside it, and this is now also where it's defined.
I could have used a class constant definition, but chose for enum because there will be a number of them that are all related.
I've decided to let the class handle all pins. And then learn the class how to initialise them, instead of doing that in main(). Exception: I haven't told the class about the ~hold and ~write pins yet. I'll do that when (if) I introduce support for protection.
This is how the changed parts of m25.h looks like:
class m25 { public: m25(spi_inst_t * spi, uint sclk, uint mosi, uint miso, uint ncs) : spi(spi), sclk(sclk), mosi(mosi), miso(miso), ncs(ncs) {} /// ... uint8_t init(); uint8_t rdid(); uint8_t rdsr(); private: enum commands : uint8_t { RDID = 0x9F, RDSR = 0x05 }; // ... uint sclk, mosi, miso, ncs;
And m25.cpp:
// ... uint8_t m25::init() { spi_init(spi, 1000 * 1000); gpio_set_function(miso, GPIO_FUNC_SPI); gpio_set_function(sclk, GPIO_FUNC_SPI); gpio_set_function(mosi, GPIO_FUNC_SPI); // Chip select is active-low, so we'll initialise it to a driven-high state gpio_init(ncs); gpio_put(ncs, 1); gpio_set_dir(ncs, GPIO_OUT); return 0U; } uint8_t m25::rdid() { uint8_t retval = 0U; uint8_t buf[3]; // command uint8_t cmdbuf[] = { RDID, }; setDataBits(8); select(); spi_write_blocking(spi, cmdbuf, 1); spi_read_blocking(spi, 0, buf, 1); // Manufacturer Identification spi_read_blocking(spi, 0, buf + sizeof buf[0], 2); // Device Identification (Memory Type || Memory Capacity) deselect(); return retval; } uint8_t m25::rdsr() { uint8_t retval = 0U; uint8_t buf[1]; // command uint8_t cmdbuf[] = { RDSR, }; this->setDataBits(8); this->select(); spi_write_blocking(spi, cmdbuf, 1); spi_read_blocking(spi, 0, buf, 1); // Read Status Register this->deselect(); return retval; }
The main():
int main() { m25 *eeprom = nullptr; setGPIO(); eeprom = new m25(M25_SPI, M25_SPI_SCK_PIN, M25_SPI_TX_PIN, M25_SPI_RX_PIN, M25_SPI_CSN_PIN); uint8_t retval; retval = eeprom->init(); retval = eeprom->rdid(); retval = eeprom->rdsr(); delete eeprom; eeprom = nullptr; // ...
As usual, the VSCode project (also directly importable into CLion - see shabaz ' comment in post 2). It contains the compiled uf2, if you're not into building from source: