Tuesday, July 26, 2022

Testing Embedded C Libraries--Who Cut the UART?

Highty Ho Boys and girls, more embedded C fun in the land of audioDiWhy. 

I'll be back with more hardware builds next month, but for now I continue to experiment with embedded C code for the Raspberry Pi RP2040 processor.

This week I ported an  AD9833 library I wrote for Atmel MPU's to RP2040. 

It's ready to test--however the new library has 25 methods and counting. How do I test the library without having to rewrite and recompile main.c over and over?  

I used UART.  This is the protocol that flowed between your 80's era PC clone and a <15K baud dial up modem. Useful things stick around; UART is simple, well documented, and supported in some manner on many computers and development boards.  

An informative webpage regarding how UART works is here.  


GET IT WIRED UP

RP2040/pico SDK makers can configure serial via USB configured through CMakeLists.txt but I found serial UART from my RP2040 developpment board simpler and more accurate once everything was set up correctly.

I am using a Raspberry Pi for sending and receiving UART serial communications from development boards. Setting a Raspberry Pi 4 up for serial wasn't as simple as I thought it'd be, but the video here helped. 

To wire it up:  

I created a 3 conductor wire with expando sleeving and dupont connectors for TX > RX, RX > TX, and GND < > GND. Note that you must connect ground between the host and the development board for any of this to work.

Assuming you are using my code: there are different UART pins on the Pico; you have to use the right ones. I used GPIO "0" or Pin 1 for RX; GPIO "1" or Pin 2 for TX, on the PICO. This conforms to defaults set in pico.h in the SDK.

On the RPi, I used GPIO pins 14 for TX and 15 for RX.  This is UART0.

On the RPi4, install minicom if it's not already there.  Settings are 115200 baud rate, and 8n1

To this end, make sure your CMakeLists.txt has "I want UART for serial, not USB" set correctly:

#control USB output--1 means on.

pico_enable_stdio_usb(${PROJECT_NAME} 0)

pico_enable_stdio_uart(${PROJECT_NAME} 1)

Wiring up the Pico for UART....


I use SSH to get to this Raspberry Pi SBC and then use Minicom as the terminal program


USAGE

UART and so on in the Pico API are documented here. This is a subsection of the PICO hardware API docs, here.

You can get the UART.c and UART.h files used in this post, ready to drop into your project, here. The basic use case for the library is documented in the library's UART.h.  

Here is basic syntax:

/*

put this in endless loop in main

READ_UART_BUFFER; 

// trap a matched string you type into your terminal program:

printf("%s\n",uart_buffer); // show what you just typed

    if (UART_READ("REBOOT"))// reboot right from minicom!!!!

        {

        reset_usb_boot(0,0);  reboot PICO

        } 

    else

        {

        // any other entry into the buffer does whatever is coded in              //here.

        }

    }

*/

AD-WHY? C WHAT? DEVWHAT? 

Why do any of this at all? (the audiodiWhy mantra....)

The AD9833 is a function generator chip--it has a reasonably wide frequency range (.1hz to 12.5Mhz), multiple waveforms, reasonably low noise, 2 registers for phase, sleep modes, and so on. I hoped to build a volt/octave FM synthesis board and other waveform generators using AD9833's along with  RP2040 development boards; for that I figure I need a working AD9833 library.

There are already Arduino libraries for the AD9833 IC (here), and I assume other popular microcontrollers, but maybe not for the RP2040. (Really? Not sure. I didn't en endlessly google RP2040 C AD9833 or whatever....so maybe a working PICO <> AD9833 C library was already out there? In any event, it wouldn't hurt to roll my own as a learning experience....)

A few mornings later the library was written and I was ready to test...so I started modifying main.c to test each method.

So:

tri(); 

after recompiling changed the waveform at output of the AD9833 to a triangle.  Works!  

Next in main.c I added

squarehalf(); 

and recompiled; this altered a square wave to .5* of the frequency found in either frequency register.  This worked as well.

phase1(1300); 

along with recompiling put a 14 bit phase value into the IC's phase 1 register.  That didn't work quite right so i tweaked it, recompiled, and after that yes, it worked.

You get the idea.


AD9833 Breakout Board

With all of these methods to test, modifying main.c, then re-compiling, then re-uploading to the Pico over and over became tedious.

How could this be streamlined?

For inspiration I didn't go out in nature--I didn't breathe the fumes--I didn't even talk to fellow geeks.  

Instead I turned to youtube. Specifically the video here

The content creator uses a UART serial stream to turn off and on LEDs on a Pico dev board, but as he mentions: there are other use cases. Testing libraries could be one of them.

So: I got this going on my bench. 

After configuring a Raspberry Pi 4 I had lying around, I wired the UART TX and RX to the RX and TX on the Pico following the steps at the beginning of this post.

Finally I SSH'd from my Ubuntu bench virtual machine to the Raspberry Pi, then once shelled in, ran minicom. Minicom of course can send data over serial by typing characters into its terminal, which I did to kick off each function under test.  

CODE ME UP ANNIE!

In a header file I called uart.h, at the top of the file, I declared a 1K global array, then wrote a global macro to compare a scanf statement to the contents of the newly created buffer. This almost straight out of  the aforementioned "low level learning" video, so again, if you've made it this far in today's post, you should consider watching it, the video is here.

// global buffer for uart reads

char uart_buffer[1024]; 

//define macro for endless loop in main

#define READ_UART_BUFFER scanf("%1024s",uart_buffer) 

//test to see if string X is in buffer. 

//Do whatever is in curlies {...} if there is a match

#define UART_READ(X) strcmp(uart_buffer, X)== 0

in main.c's main loop, I trapped a match between a string and what is in the uart_buffer.  

while (1 < 2) {

    READ_UART_BUFFER;

    if (UART_READ("TRI")) //gen tri wave

        {

            AD9833_Tri();

            CNTL_SEND; //macro to send via SPI the control uint16_t 

        }

AD9833_Tri() was supposed to change whatever waveform the AD9833 was generating to a triangle. Hooking the IC's output did the sine change to a triangle? Not at first, so I stopped and worked on the method--and no need to change main.c--and quickly had the function up running.

Next I expanded this paradigm so I could extract parameters from the serial data. UART only sends strings between systems, so, how could I trap numbers (decimals) to use as function parameters?  

For example, I wanted to test the AD9833's  FREQ0 register with 0x4000 0x4000.  Right after, I wanted to test 0x4000 0x5000.  

I could have written switch statements, or a lot of if ()'s in main.c.

Instead I created a new file, uart.c and wrote functions based on strncmp--on a match, the functions  extracted the ASCii style numbers; turned them to decimals and sent the uint32_t's to a function under test.  

Here's the code:

uint8_t compare_strings(char *a, char *b, uint8_t len)

{

   uint8_t x = 0;

   if ((strncmp(a,b,len))==0)

   {

     x = 1;

     return x;

   }   

   else

   {

      x = 0;

      return x;

   }

}

//////////////////

uint32_t dumpleft_conv2dec(char *hi, uint8_t chopleft, uint8_t digits)

 {   

    uint8_t i, len = 0;

    uint32_t result = 0;

    char *x = hi;  

    uint8_t count = 0; 

    count = chopleft;

    do

    {

          {        

        *(x + count - chopleft) = *(x + count);

          }

          count++;

    }

    while (*(x+count) != '\0');

   

    //deal with end of string 

   if (*(x + count) == '\0')

    {   

    

        *(x + count - chopleft) = '\0';         

    }

  

    //turn ASCII numbers into a decimel number

    for (i=0; i< digits; i++)

    {

      result = result * 10 + ( x[i] - '0' );

}

        return result;  

 }

compare_strings matches string array *a to *b from left to right, up to len characters.  If there is a match, it returns a 1. dumpleft_conv2dec then strips whatever characters I didn't want from the left side of the string (chopleft), and turned the rest of the string into a decimal value of length digits (keeping in mind that char "1234" did not directly translate to dec 1234--ASCii uses different codes for decimal characters!) 

 Now I can type FRQ02000 into minicom and main.c finds a match, gets rid of the "FRQ", and turns 02000 into decimal 2000.  

From here it's trivial to use this as a parameter to test a function:

 char f[4] = "FRQ"; // this has to be +1 greater than # of chars when creating array    

        if (compare_strings(uart_buffer,f,3)) // compare first 3 chars

        {

            uint32_t rez = 0;

            rez = dumpleft_conv2dec(uart_buffer, 3, 5);

            AD9833_LoadFreq0(rez);

           FREQSEND; //macro to send via SPI 4x freq bytes to register

        }


Ha! This worked! 

THE BIG LIMITATION

Putting a scanf() into the loop in your main.c means your loop will stop at the scanf(), waiting for input. This means your main.c loop won't loop. 

This worked for testing this library, but for other applications, say, reading a rotary encoder, probably not--you need your loop to loop! For that I can see using an interrupt and not scanf() when UART data comes in. 

I will probably experiment with modifying the code to use interrupts in an upcoming post.

ARDUINO AGAINO?

Another use for the UART paradigm is to run a high power processor on the back end and use something TX ready, like a Pro Micro, on the front end/user interface. The Ardunio handles the switches, pots, LEDs and OLED displays. 

As long as the end user can tolerate a brief delay--for example, a waveform that takes 100-200ms to change--an Arduino could send the same ASCII strings to the backend processor used during testing--we already know it works!

However, I have a faint recollection that RX and TX commuincation step on Arduino bootloaders--so you can't program an Uno R3 with the RX and TX pins connected to something sinking current.  Why do I think that? I don't remember, and maybe I'm wrong.  I hope I am. I guess we'll see.



Sunday, July 17, 2022

Embedded C--Recursion, C Macros, Stupid Stack Tricks, and a Clever Hex to Binary Function

....if that blog post title doesn't make you want to watch paint dry then what will?

Working on an AD9833 library for RP2040? Maybe next post....getting there....


Onward!

I am always trying to sharpen my Embedded C skills, the programming language I primarily use to program MCUs.

This week I continue to work on an AD9833 SPI library (already written for Atmel 328--here), ported to work with 32bit MCUs like the RP2040. 

Today though I went down the rabbit hole, trying to find the simplest function for hex or decimel to binary conversion for printf()'s 

Why? Why not?

A lot of embedded C programming (and digital electronics in general) comes down to sending 1's and 0's from one component to another. 

Doh?

However at times I can visualize what 0b0001011001110111 is going to do when viewing the dataflow on a logic analyzer 

Something like 0x1677 doesn't fire up my neurons the same way. 

Is that just me? A lack of experience with hex? Maybe. 

Unfortunately, C has no easy means I know to printf() a binary number--hex, yes, decimel, yes, floats, yes, but binary, no. 

So, it's a matter of finding a hex to binary function I like online and stealing it? Sure. 

I found plenty of C hex to binary functions online that start with a string array and produce binary output as a string (for instance, here), but for what we embedded C folks work on (mostly numbers, not a lot of strings) that's not much help. 

I needed the input parameter to be a number, not an array.

After a bit more searching I found a brilliant little recursive function--original post is here.  

It's only 4 lines of code!

#include "hex2bin.h"

void hex2bin(int16_t h)

{

if (!h) return; 

hex2bin(h>>1);

printf("%d",h&1);  

return;

}

It works!!  

Orig number:  0xdc or 7618976
11011100
  

How does this wonderful fragment work?  At first I didn't care, don't look gift code in the mouth? 

But then it started to drive me crazy. I could see no logical reason why bin2hex() should not present its output in the wrong order!

Why did I think that?

once you run this

h  >> 1 

The number you're converting (say, x = 200) decreases down to 0, so you're done. 

But, if you don't recurse, the printf()'d output shows an incorrect binary value because it's backwards; for example, 0b1100111 is output when you wanted 0b1110011.  

However the 4 line fragment doesn't do this--somehow it prints the final result in the correct order.

But how, Scotty, how?

Sadly Cap'n Kirk's C skills were shit, but could he kick ass with Esperanto....


Turns out, it's a call stack trick--using Codeblocks, in debug mode, with the stack and watches debug windows visible (good tutorial for Codeblocks C debugging is here), it was easy to see what was happening.

I created a simple (and very similar) function that subtracts 20 from "h" which in this case is 220:

void hex2bin(int16_t h)

{
printf("h is: %d \n",h);
if (!h)
{   printf("IN RETURN. H is %d \n",h);
    return;}
else
{
    printf("not there yet \n");
}
hex2bin(h - 20);
printf("h is: %d \n",h);

}

The stack looks like this--the call stack loads its data "upwards"--so the values of h - 20 appear in reverse order:


  

Here is the output from the hex2bin function, called from main.c, with a parameter value of 220:

0bh is: 220
not there yet
h is: 200
not there yet
h is: 180
not there yet
h is: 160
not there yet
h is: 140
not there yet
h is: 120
not there yet
h is: 100
not there yet
h is: 80
not there yet
h is: 60
not there yet
h is: 40
not there yet
h is: 20
not there yet
h is: 0
IN RETURN. H is 0
h is: 20
h is: 40
h is: 60
h is: 80
h is: 100
h is: 120
h is: 140
h is: 160
h is: 180
h is: 200
h is: 220

The trick: when the code runs the printf() over and over, printf() starts at the top of the stack and pops the data off from top to bottom--from 20 to 220--which is the same behavior as hex2bin(), giving us exactly what we need.

Stupid stack tricks!  It works!! Genius!!!

Experimenting with this I found that any intelligently written recursive algorithm that stopped when a true was reached, with return statements in the right spots, would load the stack in the same manner and thus allow the function to return data backwards. I figure other use cases are possible--for instance, load an array in reverse order. I will experiment with this....in the meantime, it shows how to really master C, you have to know how the underlying hardware works.


Not done yet! this is a recursive function, so, how could I put a 0b in front of the 010111, and an \n at the end, and any other window dressing needed? 

Since the 4 lines recurse, I couldn't put more printf() statements elsewhere in the function--I ended up with a whole bunch of 0b's at stdout each time the function recursed--something like

0b00b10b00b10b10b1

Yuck!

I ended up using a macro with a parameter in the .h file--another stupid C trick I didn't know about; but now I'm playing with power.

#include <stdint.h>
#include <stdio.h>

#ifndef HEX2BIN_H_INCLUDED
#define HEX2BIN_H_INCLUDED

//here's the macro....watch the line wrap....
#define HEX2BIN(X) printf("Orig number:  0x%x or %d \n",X);printf("0b");hex2bin(X);printf("\n")
 

void hex2bin(int16_t h); // c


#endif // HEX2BIN_H_INCLUDED

the #define prints a 0b in front of the conversion, and a carriage return at the end. Took me a bit to get the syntax right, but now, I know it.

Then I could call the macro from main.c arguing in a value of 220:

#include <stdio.h>
#include <stdlib.h>
#include "hex2bin.h"

int main()
{
    HEX2BIN(220);
    return 0;
}

Yeh baby!  Seems working!!!!  Get bin2hex C code from Github here

OK enough C for a Sunday. I worked most of the day digging into this. Did I get outside the house much? Nope.

 A very fine day it was. 

Monday, July 4, 2022

EuroRack Easy Mult and Getting Front Panels Fabricated--Cheap!

 I said last time I'm taking a break from digital, what's easier than a passive mult for a Eurorack Synthesizer?




You can buy these online, but making your own is easy and inexpensive. Get the gerber, schems, etc from my wonderful sponsor, PCBWAY's, project page, here.

USING THE MULT: The top 3 jacks and bottom 2 are wired together, but if you insert a plug into the 4th jack down, it becomes a 3x3. 



As per the post here, PCB material can make great front panels, but sometimes it's hard for the fabricator to know if you want a front panel or if you just forget to include traces in your gerber (!).  

I get around this by putting a small SMD pad on the front:


.....as well as a few fake parts--usually pads for SMB resistors--on the back, along with some fake/meaningless silkscreen legends.  

This usually gets fabricators to put their job number on the side that users don't see; in my experience their job number gets added to the "busier" side of a two sided board--so put a few fake SMD and traces on the back of the front panel.  

Finally, equest that your front panel be done up in black....


As usual, thanks to PCBWAY for sponsoring this blog and keeping it all going.

Now, your front panel goes through the fabricator's algorithm. Before long you have a ready to use front panel.

(quick post, thank goodness.....) 

Again, the link to the Eagle files and gerbers--the PCBWAY project page--is here. I include 2 front panel variations, one has a center drill for your Eurorack screws, the other has a milled oval. So look for 3 gerbers on the project page. The front panel that has two milled ovals for rack screws will be a bit more expensive to fabricate but will give you more options as far as where you can rack mount it. You may want to change the location/size of the top and bottom drills, or change the milled oval, but for what I do, the less expensive to fabricate, simple drilled version worked fine.

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?

JTAG to SWD Converter

Readers: If you'd like to build the project featured in today's post, please go to PCBWAY's Community pages--gerber file, KiCAD ...