Sunday, June 12, 2022

RP2040--SPI "Wrapper" Library for Embedded C

This time I continue working with Raspberry Pi Foundation's RP2040 MCU for future in audio projects.  

Over the last few mornings I created a embedded C library for using SPI peripherals for this MCU.  


Breadboard time....

I couldn't locate many videos about programming RP2040 SPI using C; one I found covered both MicroPython and C. It's from Shawn Hymel--this guy has good stuff--video here. Also, the YouTube channel "Life with David" has some good RP2040 content (such as an interesting arbitrary waveform generator video how to--here) but so far not a lot about SPI; David concentrates on the RP2040's PIO features, primarily.

Using those videos, as well as the (surprisingly abstract) examples found here, and the SDK SPI documentation from Raspberry Pi Foundation, I ported/rewrote the Atmel 328 SPI library I create a few years ago (here) to code for the RP2040.  If you are new to SPI and embedded C, you should read up on SPI first--good article from the Sparkfun folks: here

After a few mornings, I got it to work.  

Proof of concept--1K triangle wave created with the library discussed in this post and an MCP4921 SPI DAC. Code is here.

There are differences aplenty between Atmel 328 and RP2040 hardware and firmware. For example, the RP2040 supports 16 bit SPI communication; as far as I know, the Atmel 328 natively only supports 8 bits. The RP2040 has 2 SPI peripherals on-chip, the 328P has one. 

Pulseview is open source logic analysis software; I used it with an inexpensive Saleae clone to see what was happening with SPI at a low level. A logic analyzer is essential for debugging libraries like this one.

One of the main differences I found is that the RP2040 requires buffers for SPI data being sent and returned; this is required by the Pico SDK

This was easily accommodated; I created a suitably sized variable or array, then, when calling a SPI write method from the SDK, I used the address of the buffer to pass the data to the peripheral:

void  SPI_TransferTx16_SingleCS(spi_inst_t *spi, const uint16_t data) // cs low, 2 bytes, cs high)

 {   

  gpio_put (cs_pin,0);   

          spi_write16_blocking(spi,&data, 1);  

  gpio_put (cs_pin,1);

 }


The number 1 you see above indicates how many words occur between the CS down and CS up; so it was  easy to create another function where you can argue in as many bytes as needed:

void SPI_TransferTx8_variable_num_words(spi_inst_t *spi, const uint8_t a[0], uint8_t numwords) 

// 8bit write, cs down; variable # of bytes before CSup 

{

    gpio_put (cs_pin,0);   

  spi_write_blocking(spi,a, numwords);

          gpio_put (cs_pin,1);

 }


I already had a single method MCP4921.c file I've used for Atmel 328/embedded C (here), but since the RP2040 can accommodate 16 bit words for SPI, I simplified the code for the MCP4921 DAC 12 bit writes to just three lines:

void write4921(uint16_t data4921)

    {

     // code assumes LDAC (4921 pin 5) tied to ground

     data4921 &= ~(1 << 15); // must always be zero 

     // buffered ref in, gain = ref; no shutdown

     data4921 |= (1 << 14) | (1 << 13) | (1 << 12) ;  

     SPI_TransferTx16_SingleCS(spi0,data4921);    

    }


To test and troubleshoot, I used PulseView, the amazing and extremely useful open source logic analyzer. Using Pulseview I could quickly see if a given library call worked or not:

I tested sending hex FF, F0, and 03 (so 24 bits) between a single CS down and up.  Yes, it worked. However, SPI tests don't reveal useful information without an appropriate peripheral connected to the MCU. So, I tested this write method using an AD5761, which requires 24 bit words for each SPI transmission.

I posted the initial build of the RP2040 SPI library "wrapper" on github, here. I am calling this a wrapper because RP2040's C SDK already has functions for SPI, but I wrapped them up into something more along the lines of how my mind works. 

I didn't have any suitable SPI chips for reads/MISO, so, as of the writing of this post, I am guessing at those functions. I have ordered a few MCP3001/3002 IC's (SPI ADC). I will test with that in a week and update the post and/or the library once I am sure those functions are debugged.  Update: reads work!


I got the MCP3201's on my bench and yep, the functions for MISO/ADC read worked first time.  You ca n see the wiring I used for the ADC chip; I added MCP3201.c and .h files to the github SPI repository. 

One more tidbit: VSCODE, the IDE I chose for this RP2040 toolchain, supports a Microsoft technology called Intellisense

Among other things, Intellisense allows the user to hover over a method and see details about its use. 

OK how did I add Intellsense detail to the 2040 SPI C library?  It's easy: in my declarations (specifically: SPI_rp2040.h) I commented immediately above where I defined the function. But I added no comments on the same line as the declaration: 


These comments appeared via Intellisense when I hovered over a function in Visual Studio Code:


                                 

Next, onward to an ADC C library for RP2040.  The RP2040 has 3 12 bit ADC's, a built in temperature sensors, and some interesting features like ADC round robin. Update: done, here. 

In general, in the coming weeks and months, I will probably port more Atmel 328 libraries I have already written to this new MCU, and try to improve and optimize the code in the process.  Geek out, baby!




No comments:

Post a Comment

EFM/KORG770 VCF--BUILD: yes, SOUND: no

Ahoy!  This time, I wanted to refine the quick prototyping idea discussed midway through this previous post : Minimal breadboarding--I hate ...