Easy SPI C++ Interface

I am using W25Q16JV as SPI Flash and for this purpose I need a generic SPI driver that I can use in a W25Q16JV Driver. This is part of my Abstraction Layer in C++ where I add a little extra code to avoid having to deal with HAL/HW Interface on every SPI device I use. In fact, I prefer to think of SPI as a duplex serial interface.

Speed

W25Q16JV datasheet state a speed of 133Mhz, but as I tested max speed with H723 it failed. I set SPI clock to 250Mhz and SPI to 125Mhz which should have worked I thought – but as OpenAI pointed out the max speed using single SPI is 50Mhz and you can “kind-of” read that in the datasheet. I managed to get the SPI working at 31.25Mhz and tested it closer to 40Mhz, I actually set it to 50Mhz, but that did not work – so at the end I left it at 31.25Mhz using 250Mhz as SPI clock and 8 as prescaler. I understand from OpenAI that this is a common problem.

I am a bit dissapointed, but to be honest 31.25Mhz is more than sufficient speed. If I need more speed I can implement Quad or Octal SPI which actually support 133Mhz on Fast Read. It is also possible to get faster spead using only Fast Read commands I understand, but I am not bothered. This was an interesting lesson learned because it is the first MCU where I actually could have used higher SPI clock speeds.

31.25Mhz is actually 3.9Mbyte per sec and we only write/read very small blocks so speed is sufficient for reading/saving config or storing new firmware images. I am still waiting on PSRAM which also has a stated speed of 133Mhz, so will try that later. The only timing we need to be aware of is then we erase the chip or larger blocks – full chip erase is probably ca 0.5 sec – I will test later.

Capacity

16Mbits is 2Mbyte of extra Flash data. I was considering a larger SPI, but to be honest I am not sure what to use what I have for and I don’t need a lot of Flash I don’t use. In fact for config I could have borrowed a few K of internal Flash, but I have in the back of my mind that I might need SPI Flash for bootloader as I have not investigated the details of the H723 bootloader yet.

Read JEDEC ID

JEDEC is a standard established in 2003 that allows you to send code 9F and get a two byte standard telling you memory type ID and capacity ID back. This means you can use one of many SPI devices with the same footprint and depend on Firmware to detect what is mounted. Winbound have several other functions as well for the same purpose, but JEDEC will work on non-Winbound as well (I have tried).

Class Model

“al” stands for “Abstraction Layer” and I use that prefix on modules where I expect names to conflict with HAL/Driver names. I removed the “H” from HAL because AL can be Hardware and/or Software abstraction. The UML model above illustrates the 4 layers we have:

Barebone SPI means SPI interface in assembly or very low, level C where you mingle the registers yourself. I will NEVER recommend that unless you are desperate for space or speed. Back in the old days I wrote a lot of assembly myself, but these days you will need to argue hard to add assembly.

HAL – Hardware Abstraction Layer type of drivers are provided in C for most MCU’s these days. I always use these unless I can make a decent argument why I should not.

alSPI – This is my own Abstraction Layer in C++ and this is 99% generic. I preffer to use HAL and tools provided by the vendor and then put stuff like alSPI on top of that – the cost is that I have to accept 1% of code to be vendor specific because I need til “wire” alSPI to pins used by HAL etc. But, it is ONLY the init/wiring that is vendor specific, meaning code that uses alSPI is safe.

So – does alSPI use bit-banging, interrupts or DMA ? It uses HAL and I don’t care unless I need to care if it becomes to slow. And on a SPI Flash it is only the time used in Erase Chip you need to have control over.

Also keep in midn that a C++ class can be bare bone. If I need to go barebone I would do so inside a C++ class/function.

WQ25JV16 is the Winbound SPI Flash driver in C++.

On 3xCAN I have added lwIP (Ethernet), RTOS, HAL and Flash-SPI and am currently using 139Kbyte FLASH with 373Kbyte free. lwIP answer for a lot, but I also need to add CANopen, J1939 and ProfiNET stacks later – I estimate total application might pass 200Kbyte on Flash. Application uses 48Kb SRAM + 32Kb (lwIP) – I still have 581Kbyte SRAM free. CPU wise I will be mostly in idle mode – I have considered a smaller MCU for this board, but the 3rd CAN interface is worth a lot + once you start using a MCU you also start buying bulk that bring it down in cost – not to mention the hours you spend making software for that MCU.

C++ Performance Trick

C++ code is often faster than C code on API layers because of virtual functions and how things work in C++! If you have layer upon layer in C you need to have a function that call a function on the next layer and the same cascade with callbacks. In C++ you implement Send in alSPI and as you call Send() on W25Q16JV it actually calls alSPI::Send directly. The same goes for events coming back through virtual functions. So, C++ can allow itself a much more abstracted API without paying much performance penalty . just know what youre doing!

Myself I use what I call “Embedded C++”. This is C code + a subset of C++. Modern C++ also cosist of a lot of features I would never use! In embedded development you seriously need to know what you are doing and why!

Leave a Reply