Saturday, July 2, 2022

RP2040--I2C Library for Embedded C--The Run Screaming Post

Welcome back to the land of lost readers!  If you are an embedded systems ("IOT") hobbiest, the embedded C discussed in this post will probably make you run screaming; there are higher level languages like Arduino Sketch and MicroPython that, for robotics, audio, getting Rover to use her litterbox, making sure your garage door is shut, etc., "just work"----and are far easier to code. 

If you're an electrical engineering professional and you need added speed from compiled C--you probably know C, inside and out, already, much better than I do; you have been coding C for years?  

I'm in the middle, with unfortunate occasional regressions towards newbie; interpreted languages were too slow for my projects and at times learning Arduino C abstractions was taking longer than coding using a lower level language. 

I like a challenge; C can be that for sure. 

Soooo....hello C.....frustrating, puzzling, and wonderful. 

If you're still with me, read on.....  



THE CHALLENGE

I am transitioning from Atmel 328P embedded C (used in Arduino development boards such as the Uno R3) to the RP2040, an ARM dual core M0+ based MCU from the Raspberry Pi Foundation. 

Porting code from Atmel to RP has been--challenging--since these processors are fairly dissimilar. 

I have already set up a toolchain (here), built an inexpensive step debugger (here), and coded libraries for the 2040's built in ADC's (here), SPI abstractions (here) and this time: a library for the I2C protocol.  


AD4728 cheap I2C breakout board

I2C WRITE

The RP2040 API has several I2C functions already good to go (here), but what fun would it be just using that?  

To better understand how the RP2040 hardware I2C API works, I wrote my own I2C library abstractions using their I2C abstractions.  

(Have you run screaming yet? No? Congratulations, you have passed the Frank Zappa test. Let's keep moving forward.....)

The challenge: could I get an MCP4728 quad I2C DAC breakout board ("BoB") to produce analog voltages I can see on an oscilloscope. 

I had this working for the Amtel 328 (get that library here)--one of my first embedded C coding experiences, and yes that's some crap code!; the MCP4728 IC does a lot but my library for it is stripped down; it provides only a method to send 4 simultaneous values to the ICs four DACs and not a lot more.

If I could get that to work with an RP2040 that'd be good enough, I figured. 

Porting existing MCP4728 Atmel C code took a few hours...not too bad. 

The SDK for RP2040 mandates that write values be send to a buffer (in other words, the values for your DAC write must be written to memory).  For that, I crafted an 8 byte array in main.c.

Then I passed this array to other functions--necessary to get the C code to work in any form. This was not as hard to code as it was when I first started on my Pure C adventure (initial post is here; I feel I've come a long way--if you're ditching python or sketch for C and C++--stick with it--it gets easier as you go.)

//write I2C bytes then stop

//arguments:

//*i2c    i2c0 or i2c1

//addr    address of I2C chip 

//*src    pointer to buffer that has the data you want to write

//nbytes  number of data to write, not including address

uint16_t I2C_Write_nbytes_stop(i2c_inst_t *i2c, const uint addr, const uint8_t *src, const uint8_t nbytes);POINTER ALERT!

AN ASIDE IN ITALICS

Pointer warning for anyone getting started with C: Yeh, they suck. Easy for a pro. Hard for a beginner.  Attention youtube super duper content creators: "pointers are simple". Baloney! To play a flute, blow here, and move your fingers here. Sounds great! My 2 cents: pointers are like so many other things in life: How do you get to Carnegie Hall?  Practice, practice, practice. 

And! For anyone wondering why you need pointers in C at all: for one, the RP2040 SDK requires you to write your code this way--otherwise you get nothing! 

So roll up your sleeves....

 

MCP4728 under test...

I2C READ

After seeing 4 stupid voltages on my scope it was time to move on to an I2C read. 

 I haven't done a lot with I2C for injesting data; built-in MCU ADC or SPI ICs tend to be simpler to code so that's what I've used to date.

Nevertheless I wanted to make sure I2C read functions in the library worked. I bought (for USD $1.50 each) a few PCF8591 breadboards from tayda to test the code.

It's a quad 8 bit ADC, see the datasheet here. These BoBs do a lot--differential inputs, a DAC output, etc. Opting for simplicity--I wanted to see if I could read a single channel value into the MCU then output it via UART to a Raspberry Pi 4 SBC--good enough.  

Main problems: the chip's address is 0x48 and the read/write bit is ignored (it's in the datasheet, but I missed it) and from trial and error and looking at data with a logic analyzer it seemed (?) to get a good read from the PCF8591 you have to read in a byte of data, ignore it, then get the next byte...I knew that..... 

Once it was on the bench I used Pulseview to analyze the logic:

I am realizing a logic analyzer is as important to digital electronics as a scope is to analog. A good place to start is Sigrok.

Simple issue--I had the address wrong--0x90, oops, not 0x48--easily spotted and fixed.  

For logic analysis, I am using an $8 saleae clone--but the Windows 10 driver keeps quitting on it, and the application crashes from time to time. I am debating laying out big money (>$1K USD!) for a non-clone....yes a real Saleae analyzer--what's my time worth?  Update: I bit the big one and bought a $1500 Saleae Pro 16.  Not a clone! Who needs to pay for food--put it on the credit card! A lot of money, but, I figure it's probably the last logic analyzer I'll ever need to buy).

PCF8591 Quad 8 bit ADC on a BoB. $1.50USD each--incredible value
!!!

"Seems working". 

If you want to take a look at the library (congrats for hanging in there) it's on github--here

With all of this in mind, I am beginning to hone in on a personal, organizational standard for ongoing embedded C projects:

  • Each IC that needs code (ADC chips, DAC chips etc) gets its own .c and .h files. This is for portability/reuse in other projects.
  • Any global variables needed for an IC, such as a structure to set and change bits for the IC's registers, or an array to hold the summed output of the structure's content, and so on, is declared as a global variable in the .h file associated with the IC (so, in MCP4728.h, AD1115.h, etc). These need to be declared as static, extern, and so on, as needed. I read in places this is not a C best practice. But then I see a lot of .h files and main.c's that use this convention. Sorry.
  • Structures for ICs and buffers used to ultimately communicate information to SPI, I2C etc., should be reachable via a pointer instead of the other ways to do it, when possible.
  • Limit the amount of function returns when it makes sense--again, modify memory with a pointer.
  • The methods to communicate with ICs (SPI, I2C) are in their own libraries; not in the IC libraries themselves.

I'll see if I can make this work in upcoming projects....

Enough coding for now! I had plans for my next real digital project--some sort of gee whiz VCO thing with an RP2040--but right now I'm kinda sick of it, so I might lay out some analog boards just for fun. 

Fun--that's the idea right?

2 comments:

  1. Could this be used as a library for Arduino's IDE? I'm sorry i'm very noob.
    I also was using a 328 and now trying to step up to the Pico! :-S

    ReplyDelete
  2. Hi locorocu: no need to use the library I coded--wire.h, the Arduino abstraction to talk to I2C peripherals, appears to already be good to go for RP2040: https://forum.arduino.cc/t/raspberry-pi-pico-i2c/918824/5

    ReplyDelete

Rotary Encoder Expermenter's Board: Improving the Hardware

Quick one this time....I have posted a few projects lately that incorporated a Raspberry Pi Pico, rotary encoder, and .96" OLED:  here ...