Saturday, October 16, 2021

Dirty Digital LFO Rebuild: SMD Analog Board--Trouble Shooting Audio Op Amps

 Hello Again! 

A few posts ago I got an LFO working based on Atmel 328P and some Embedded C Code. The module (post here) works, but uses four--count 'em four--thru hole PCBs, electrical tape, plenty of "temporary" fixes, and a heapin' helpin' of elbow grease.  

I know from previous experience that these butt sloppy modules stop working, or worse.  

I can do better!

OK, let's build this module again but this time with higher quality.  

I will also add "BOWAL" features (creating bias offsets at the inverting op amp inputs) adding much needed craziness to the module's incoming and outgoing signals--for more BOWAL see the previous post here.

The SMD board and solder mask for today's build is courtesy this blog's sponsor PCBWAY.  Please check them out and help support future geeky posts....

Wham BAM! The new design is already back from PCBWAY. that was fast!!!!

I mostly chose 0804 SMD parts for this build, smaller than I've used before. Then I built the "analog" board using the same stencil and hot air process you can read about here.

I make front panels from PCB blanks, even before a newdesign tests working, to make troubleshooting somewhat easier. 

OK with all the components soldered in it was time to test.  

Did the analog board work first time? Nope. The input and output buffers passed nothing through to the PCB mounted jacks. However the portamento sub-circuit worked the first was time to troubleshoot!  

I'll post the schematic once the build is finished......

Make sure you have a steady platform from which to probe, solder and fix.

This design  used six op amps stages for signal conditioning. Analog signals can swing rail to rail, but our 328P microcontroller only likes to see 0 to 5V.  

How do we make sure nothing blows up? 

Op amps! 

I'd be surprised if anyone following this blog doesn't already know a lot about this component, but just in case, a series of posts about how op amps work and their primary applications begins here.

These fundamental ICs are a miracle when they work, but can drive you crazy when they don't; for a beginner they can be difficult to troubleshoot. 

So.....Here are some tips I've picked up from other techs that I used to get the analog board's op amp stages fully functional:

  • First--As always, don't guess! Don't shotgun! Don't brute force. Instead follow the wisdom of El Shango: follow your schematic and isolate the problem logically.  
  • BTW I get my kicks on Shango066. please fix things the way he does--divide and conquer.  
OK back to troubleshooting:
  • Using a scope or DVM, check that the op amp has the correct voltages at its VCC and VSS pins. If it doesn't, it won't work. Doh.
  • Check that the op amp isn't wired up backwards (so, V- rail goes to V+ etc., chip was installed upside down, etc.). That can fry your op amp in a heartbeat.  
  • If you wire up op amp power pins wrong, even if the op amp doesn't smoke, you may want to replace the op amp...I know one tech who says you should do that every time you reverse power any linear IC, including an op amp.
  • Draw a simplified mini-layout of the signal flow in and out of the stage under test, then check the signal flow carefully with a continuity tester.  
  • Check for continuity to the actual pins of the IC, not just its pad.
  • For audio troubleshooting, consider using a ProbeMeUpScotty to follow your signal path to where audio stops--where the audio quits may be where your problem begins. 
  • If you are sending the op amp DC, use a scope or DVM to see where the problem starts in the same manner.
  • Isolate each op amp stage from the rest of your circuit. I usually remove a resistor or cap between op amp stages or between an op amp stage's I/O and the rest of the circuit to do this. 
  • SMD tip: removing and reattaching small SMD resistors and capacitors has turned out (for me anyway) to be easier than dealing with thru hole removal/replacement. You need a steady hand, fine gauge solder, a magnifying glass, a fine tipped soldering iron, and of course a pair of zircon encrusted tweezers, but with the right tools it's easy and forgiving, even with really small parts.
  • Once you're sure you've electrically isolated the op amp stage under test, apply a known good test signal at the op amp stage's input (inverting or non inverting, depending on your basic design). 
  • You should see your buffered output at the op amps output pin pad--use a scope.  
  • If you don't see a decent output (0V at output, output is slammed against V+ or V- rail, output has severe oscillations, horrible distortion etc.) then you know the op amp stage under test has a problem and needs more attention. 
  • Check and double check the parts in the stage's feedback loop and surrounding I/O for issues. Wrong value? Short? Open? Design mistake?
  • You often can't scope the inputs of an op amp because they are virtual grounds. Damn! But you can sometimes use a neat trick to see what works and what doesn't.
  • Here is an example of this--in this case, used to troubleshoot an inverting op amp stage:
Wire up a clip lead to a resistor blow a test signal through it.  Now, touch the other end of the resistor to the inverting input of the op amp stage under test. If you now see your expected output (inverted of course) on your scope, you know the op amp chip itself is OK and its inverting feedback loop is OK too. You have further isolated the issue to the signal feeding the op amp stage under test.
  • Once you've fully isolated the issue look carefully for cold or bad solder joints, shorts, opens, a wildly incorrect part value, a stupid mistake in your design, and so on. It's almost always an easy fix but only once the problem is isolated.  

In my case, I found 2 problems: a resistor that was shorted (I flowed too much solder below it) and an inverting op amp input that wasn't fully soldered down (probably too little solder paste). 

At the end of it, I had a working analog board.  The "minimalist" Atmel board design for this module is almost unchanged, so hopefully the rest of this build will go smoothly....and its code is already written and works. Yeh!

BTW, I may have hit upon the analog input buffer circuit fragment that I'll use in many future designs. I use this fragment to feed analog signals to analog to digital converters (ADC's); it's simple, has a reasonably low parts count, and gives me good control over many parameters that need to be adjusted before the signal is fed into our MPU's ADC.

Here it is:

Oops should read "limits overall voltage to ADC".  

This sub-circuit has the ability to limit the overall buffer voltage output (Zener), each gain stage (R1-R4), the output impedance (R5, R6 plus next stage's load characteristics), the bias offset (R7, pot), as well as attenuate the incoming signal's overall amplitude ("Level").  

This is the fragment I am using for the post's analog board ADC input conditioning. How will it perform? Next post I will marry this analog "Pots" board to the MCU. See you then I hope! Stay safe and stay tuned.

Saturday, October 2, 2021

Embedded C--Riley Reset Revisited--Interrupt Your DIY Digital Synth Module's Logic with Almost Any Signal!

We continue on our Embedded C Atmel MCU adventure.....

Quick one this time as I build my way to an improved Dirty Digital LFO. I don't want to forget what I've done so far so perhaps this post is mostly for me?

One of my favorite dev boards for Atmel Embedded C programming is the Arduino Uno R3. To get the background on Interrupts for this board, using Embedded C, you may want to read the previous post here.  

Interrupts are key in many digital audio projects, in the case of the "dirty digital LFO" I created a waveform reset to zero buffer PCB using a 3904 transistor and a few resistors--post for that is here.  

I am now rebuilding the ddlfo using a lookup table, and I also want to make it so an incoming square wave or pulse signal resets the LFO's output frequency to zero, and finally have that work in parallel with a frequency counter (post here).

But--can we get the existing DDFO reset circuit to work with what is essentially very different code?  Yes. That's one of the major advantages of embedded C; you get something to work, then drop code and hardware fragments from old projects into new ones. Modularity is a primary reason I made the switch from a highly abstracted programming language to Embedded C for my AudioDiWHY projects.

Get down to it: The inverter/buffer circuit and PCB shown above is covered in the post here. Lots of ways to do this, but since my sponsor, PCBWAY, sent me a few 3904 buffer PCBs and I've only used one to date, I might as well use what I already have on hand.  

To make this work I created a very simple gate to trigger on a breadboard using a single .001 cap and a 47K resistor; trial and error indicated that this worked best for my modular rig. The output resistor R5 for the buffer is set at 1K, not 22K as in the original DDLFO design. For proof of concept I am using an MCP4911 DAC, which I like due to its simplicity.  

To make the 4911 DAC go, I used existing MCP4921.c and .h code from my github page, here, and modified it slightly to accommodate 10 bits at output vs. the 4921's 12 bit output. the core "wave reader" code was written from scratch.

I will post this code including the 4911 driver file when I have this "wavewalker" LFO working--should be pretty soon?

The last thing to do was put it on the bench to test:

....using this code fragment before the "main loop":

/**********INTERRUPT ********/

ISR (INT1_vect)



x = 0;

_delay_ms(5);// "Riley Reset", incoming interrupt resets LFO wavefrm to zero




/**********Set up Riley reset timer************/

EIMSK |= 1<<INT1;  /* enable external interrupt 1 (D2 pin on Uno R3) */

EICRA |= 1<<ISC11; /* interrupt on edge (trig)  */

//EICRA |= ~(1 << ISC10); /* interrupt on low (trig) */

EICRA |= 1 << ISC10; /* interrupt on high (trig) */

sei(); /* enable interrupts */

This worked. The wavewalker design, so far, starts at cell 0 of a 200 cell uint_16 array and steps through the array sequentially; there is a pause between each read. This delay() sets the overall frequency--longer pauses mean a slower frequency at output.  

So--to perform a waveform reset is pretty simple, you tell the system to start reading at cell [0] each time an interrupt is seen.  

On the bench it works, but I see an occasional need to better debounce the interrupt signal. For the LFO "wavewalker" design to date: what you see above seems good enough. 

I need to next tweak the frequency counter (post here) to multiply it by 15 or 30 (how I arrived at these  constants will be covered in a future post), then create another lookup table match the observed incoming frequency to the delay between array reads. That should allow BPM to frequency conversion for the LFO.  

This will hopefully make more sense when I post the wavewalker design, which I hope to have working sometime this month. I am now going to pull apart the breadboard dookie you see above to further tweak the frequency counter subcircuit, so what you see blogged today is all I have for the interrupt portion of the design, at least for now. 

Stay tuned.

Monday, September 13, 2021

LFO Prime--Reverse Engineering my Own Design--Works!

Want to Build The Module in this blog post? To download a Eurorack version of the LFO Prime's gerber, BOM, schematic, etc., please go to my Sponsor PCBWay's Project Site, here. To build a Frac or Large Format version, go to my github page, here

OK here's today's post:

Hello again!! 


This is a continuation of the post here. To summarize: I built a basic LFO in 2003 or so I like a lot but forgot to document any of it.  

Using a continuity tester and common sense can I recreate my own work?  After a false start (post here), answer is yes.  My First LFO--"LFO PRIME" has been successfully reverse engineered, and the new version of this old design works.

My builds always start with PCBs from PCBway.  They have fast turnaround and offer cool services like metal PCBs. Please help support my blog by checking them out.  

So what was wrong? Once I thought about how the bias portion of the design worked--I reduced the voltage of the square by about 80% then boosted both signals back up 4x--it worked.  I have found that with op amp designs you have to get the basics right, otherwise you get nothing.

The front panel was left over and unused from last time

Ready to test....there is an LED the blinks for each LFO pulse, but I have too many LEDs in my rig so I left it off this build.

Front Panel still fits....

The features: Tri or Square, Frequency control with course frequency switch, and a bias offset (bipolar or unipolar).  Three identical outputs. Super Simple.

Calibration: Build your LFO prime then attach any of the LFO's 3 outputs to a scope.  Adjust the "shape" trim until the triangle wave looks the way you want.  Next, adjust offset so when you throw the 0V/5V switch UP the peak to P/P voltage is offset to 5V.  Otherwise, the waveform should straddle ground.  Finally, adjust Speed so the frequency switch (fast/up), with pot fully CW, is whatever you want (I chose 300hz).

Mods: Download the schematic then take a look at C3 and C4. Adjust these Caps' values--larger value caps mean slower frequencies; smaller values will be faster. For perfectionists, tweak R3 and R2 to make sure the triangle and square are exactly the same (I didn't care that much).  For bias, the BIAS 0V/5V switch could be eliminated and the offset wiper tied to R6 for a fully adjustable bias control. Overall, this is a simple design, experiment and have fun. 

Again thanks to PCBWay for hanging in there while I tried to figure out my own work.  From now on I will document, document, document!

Monday, September 6, 2021

LTC2645--PWM to Voltage--Embedded C--Can We Get 10K Audio from an Atmel 328P?

Note: if you want to download the gerber for this post's MSOP .5mm to PDIP adapter board, you can get it from PCBWay's Project Page, here

OK on to the post:

Hmm...again: 10K audio from an Atmel 328.  Possible?

The quick answer is yes.

The longer answer is yes but it sounds kinda crappy.

The idea: for the last few weeks I have been working on an audio waveform generator based on an Atmel 328P  and Embedded C.

That's an 8 bit microcontroller with no DMA or I2S, and so far I've found it too slow to produce high fidelity audio--anything above about 300hz exhibits varying degrees of distortion. 

But I read (here for instance) that you can use an 8 bit Atmel Microcontroller's PWM to get up to about 8K at output by varying a PWM signal's duty cycle then turning the PWM signal back into an acceptable analog voltage using a  low pass filter.

Fine, but let's have additional fun--let's see if we can use a PWM DAC, such as an LTC2645, instead of the filter. 

What is a PWM DAC?  To understand that, you need to have a basic idea how PWM works--a good description is here.

For the PWM to DAC IC, pulse width modulated signal(s) go in, and based on the signal's duty cycle, the chip produces one or more analog voltages at its output(s). 

PWM ICs have many applications in electronics, you see them frequently in power supplies for instance.  I am not sure how often PWM to voltage is used in DiWhy audio, but what the heck, I can't see why this wouldn't work, let's try it!

Sample and Hold: I got a few LTC2645's; its datasheet looks straightforward, and the IC replaces the various filter RC components, potentially making the entire audio generation circuit's footprint small and simple. 

But one problem for a maker like me is that the LTC2645 is tiny--MSOP, 16 pins, 5mm between pins: 

The LTC2645 compared to a U.S. Nickel. For our friends abroad, a nickel is 21mm across.   

That's really, really small.

What to do? I reached out to my sponsor, PCBWAY, for help, and I designed a simple 16 pin 5mm MSOP to PDIP breakout board. Then, I uploaded the gerber. Happily I got 5x PCBs from PCBWAY within a few days (!!) along with a stencil they created for the project and got to work.  

Overall fab for 5mm MSOP's wasn't easy due to its extremely small size, but with some patience I got it working using the same hot air/SMD stencil process you can read about in the post here.  

If you want to play along at home, please download the files and gerbers for this adapter board from PCBWAY's Project pages, the gerber is here.

Next, on to some almost useless but slightly entertaining bench photos:

Not looking too bad....each board can accommodate two MSOP chips, but for sanity I just populated one half. That gives me a bit more room to tape down the PCB for fabrication.

Oops, this chip got installed upside down, oh well!

Pin time....

No photo but: After soldering the pins, I used a dremel tool to saw each board in half, and was left with 2 dusty BoB's. 

In need of some tidying up, but ready to go....

After a quick clean with flux remover, I was ready to test....

....with things not looking too bad under the microscope.

Wiring up the LTC2645 (datasheet is here) was easy; there is no programming needed, just hook up your PWM source one or more inputs, apply logic, power and ground, and use its output(s) to get a smooth uinpolar analog signal.

The LTC2645 accommodates four PWM inputs to four analog outputs. Make sure your incoming square wave is about 1Khz-5khz for the 12 bit chip; I used 3V P/P with a 1.5V offset.  Decoupling Cap for me was .2uF, but .1uF should work.

I wired it up using my trusty radioshack dev widget and an UNO R3:

OK, for test code here are some examples for waveform creation using PWM. These were all taken off the Internet; I modified each file slightly, but not much.  

The programs below were compiled and uploaded to the processor using my usual Atmel Studio 7/Atmel Ice combination. I used an Uno R3 development board.  Pin PB1 (For Arduino-ites: that's Arduino pin 9) was used as the PWM output for the top code example, while pin PD3 (which translates to Ardunio Pin 3) was used in the 10K example at the bottom of the post.

When compiled and the PWM was connected to the 2645, it produced a decent if gritty 3K triangle wave:

 * LTC2645-func-gen  main.c
 * adapted code for 328P and LTC2645 from      *
 * Created: 9/6/2021 9:46:56 AM
 * Author : audioDIWHY

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdbool.h>
#include <util/delay.h>

int main (void) {

/*set outputs */

DDRB |= _BV(PB1); 
        /*arduino PIN9--timer 1 PWM on 9,10 (PB1,2) */
DDRB |= _BV(PB2);  

    /*3.4K pwm using timer 1 */
    TCCR1A |= _BV(COM1A1) | _BV(WGM10);
    TCCR1B |= _BV(CS10) | _BV(WGM12);


  uint8_t pwm = 0x00;
  bool up = true;
        /*3.4K tri */

OCR1A = pwm; 
pwm += up ? 1 : -1;
if (pwm == 0x84)up = false;
else if (pwm == 0x00)up = true;


And this short main.c file produced a 5K or 12K ramp (which both looked and sounded pretty ratty).

/*adapted from webpage:    AtMega328 */

#include <avr/io.h>
#include <util/delay.h>

int main(void)


    DDRD |= _BV(PD3); 

/*arduino Pin 3 timer 2 PWM AVR PIN PD3*/

/* Set up the 250KHz output */
TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);
TCCR2B = _BV(WGM22) | _BV(CS20);
OCR2A = 63;
OCR2B = 0;

/* Make the 250KHz rolling */
while (1) {
if ( OCR2B < 63 )
OCR2B += 5; 

/* OCR2B += 2 is about 5K ramp.  5 is                                about 12K ramp. higher is more speed but crappier looking                                    waveform. */
OCR2B = 0;

Hey--it works....

So what's the verdict? After a morning or so working on this, sadly, this still isn't the audio frequency solution I needed.  

The LTC2645 is an interesting chip, but it's expensive (about $10USD for one 12 bit chip) and I am not sure you gain that much using it and not an RC filter.  

Maybe for certain applications where you want to turn four different PWM signals to voltages with output speeds up to 5K this chip would help. But that's an unusual requirement. 

Overall I guess we will see; the jury's still out.....

OK, enough PWM for now. Time to stop working, it's a 3 day weekend.

See ya' around....hoping your duty cycle is not too long....

Saturday, September 4, 2021

AVR 328P Frequency Counter--Embedded C--Using the Handy ICP1 Pin

Hello Again Fellow Bitheads.  If you've been following the blog for the past few months I've been coding in "Pure C" or "Bare Metal C" or "Embedded C." Whatever you want to call it, it "ain't Arduino Sketch".  

This time, I was thinking about adding a beats per measure (BPM) to frequency feature to the improved Dirty Digital LFO. Last post about the current Embedded DDLFO module's design/code/build is here.

To make this work, I will first need a squarewave frequency counter, so we can feed in 4 or 8 pulses per beat and get BPM out, so this time, let's work on the counter component of the design.

The counter works from about 2hz to about 300khz!

OK to develop this on the bench, what hardware to use? I have a few Arduino Uno R3 development boards lying around, with a PDIP Atmel 328P IC on board, so let's use that, but we will code with Atmel Studio 7 and upload our code to the MCU with an Atmel Ice Programmer. With all due respect to the Arduino community: no Arduino Sketch code will be used for this project.

Next let's go steal some open source Embedded C code. There should be lots of Atmel 328 counters coded in C to be found online, right?

Well, maybe not. Some posts I found use assembly language for AVR, not C, see the project here for instance--assembly is at the bottom. Overall I couldn't find as many C based AVR frequency counters  as I expected.

However I found the very useful post here (ElectronicWings--lots of good stuff at that site) with code for a counter based on Atmega32 processors--a good start.

I am not using a Atmega16 nor a 32, and the timers/registers in the Atmega 328P are somewhat different than the 16/32, so some code porting was needed.

Fortunately porting this code to Atmega 328P was not that difficult, taking me maybe a morning to figure out. I primarly studied the Atmega 328P datasheet, pages 96-99, and beyond changing the names of some registers most of the ElectronicWings code worked without a whole lot of modification.

The code uses a feature called an input capture pin, or ICP; something I've not used before--on the 328P that's a dedicated Pin: PB0. On an Arduino Uno R3, that's "Digital pin 8". Details about ICP can be found in the datasheet and is also covered here. The basic idea: capture the rising or falling edge of your square wave and put its value into a 16 bit register; move that register's content to a variable in your C code, then repeat the process using a different variable. 

Finally, subtract the two variables to determine the frequency.  

I also learned of an interesting associated peripheral--the 328P has a pretty sophisticated analog comparator built in you can use to capture input timing in the same manner.  I didn't use that feature this time, but I can see a lot of uses for it for what we do. More detail about the 328's comparator peripheral is here.  

I needed a display for this week's project; I used an SSD1306 OLED cheap display I had in my junk box. 

I used my prewritten I2C libary as well as the 1306 library found here to complete the C code for the counter. 

Wire me up Scotty! The "works on my bench" build is pretty accurate from 2hz to about 300khz. Not too shabby? I am manually setting the range 2-500hz and 500-300Khz) by recoding what is below then recompiling/uploading to the MCU--using the--you guessed it--"range" variable. Value 0 is for high frequences, while 1 is for lower frequencies. This could be a front panel switch, or the code could be rewritten to autorange, but for now just typing in a value and recompiling/uploading is good enough.

Wiring on the bench is very simple--I2C, power, and a feed from my "freq gen"--i.e., my bench squarewave generator (A Siglent SDG1025)

How accurate is this quick counter?  Again: good enough. It tracks pretty well for frequencies between about 700hz and 200khz for range 0. 

For range 1 2hz to about 300hz is good and it's usable to about 800hz. 

I will probably need to use a slower prescaler for the BPM clock capture, but that's for another day.  

OK, here's the main.c code.  Again you'll need my I2C libary .c and .h files and this 1306 driver added to your project before you can compile this....upload and have fun. If you come up with anything good please let me know in the comments.


 * freqcounter main.c


 * Created: 9/2/2021 10:42:24 AM

 * Author : audiodiWHY


#include <avr/io.h>

#include <util/delay.h>

#include <avr/interrupt.h>

#include <stdlib.h>

#include "SSD1306.h"

#include "i2c.h" 

 //////// range = 0  measure freqs 500-300K with pretty good //accuracy--audio and bench stuff

 //////// range = 1  freq 2hz to about 700hz with pretty good //accuracy -- LFO etc.

 //************SET INITIAL RANGE HERE************

 uint8_t range = 0;


int main ( )


OLED_Init();  //initialize the OLED

OLED_Clear(); //clear the display  

unsigned int a,c,period;

char frequency[14];

          DDRB = 0x03;

PORTB = 0b00000001;


Turn ON pull-up resistor 

NOTE! ICP1 is pin PB0 on AVR328 you knew that?


//1306 lib, set the cursor position to (0, 0)

OLED_SetCursor(0, 0);        

//SSD1306 library to print text to OLED

 OLED_Printf("AudioDiWhy Freq ctr");  

 OLED_SetCursor(2, 0);




        // set counter parameters

TCCR1A = 0;


TIFR1 = (1<<ICF1); 


Clear ICF (Input Capture flag)                                     flag; writing 1 clears


if (range == 0)


                   // Rising edge, no prescaler 0x41  

TCCR1B = 0b10000001;  


if (range == 1)


TCCR1B = 0b10000100;  // Rising edge, 256                                                    //prescaler


while ((TIFR1 & (1<<ICF1)) == 0)


/* next lines for                                                    debug.              

OLED_SetCursor(4, 0);

OLED_Printf("waiting for cptr a"); */                    


/* put capture register in a */

a = ICR1; 

TIFR1 = (1<<ICF1);  /* Clear ICF flag again */

        if (range == 0)


    TCCR1B = 0b10000001;  /* Rising edge, no                                                   /prescaler  0x41 */


if (range == 1)


TCCR1B = 0b10000100; 

                /* Rising edge, 256 prescaler */


while ((TIFR1 & (1<<ICF1)) == 0)


     /*    OLED_SetCursor(4, 0);

       OLED_Printf("waiting for cptr c"); */


c = ICR1;  /* capture 2nd up edge */

TIFR1 = (1<<ICF1);  /* Clear ICF flag */

TCCR1B = 0;  /* Stop the timer */

if(a < c)   

            /* Check for valid condition, to avoid                                  timer overflow reading */



long freq= F_CPU/period;

                         /* Calculate                                                          frequency */

if (range == 0)


/*do zilch*/


if (range == 1)


freq = freq/256;  

                         /*accommodate prescaler                                                  /value.*/




OLED_Printf("           ");



OLED_SetCursor(4, 60);




}  // end if




OLED_Printf("ReadErr"); //Counter overflow




} // end main

Code-ah? When I get more of the BPM features coded I will post the whole project to github. In the meantime: Hey, a new batch of boards and stencils are here from the blog's sponsor, PCBWAY, so some soldering is right around the corner. See ya next time!

Sunday, August 29, 2021

STM32/ARM Microcontroller Processing--How Low Can We Go?

After a few months of digging into Embedded C on the AVR 8-bit processor family a problem emerged: the Atmel 328P processor wasn't fast enough for some audio DiWhy circuits I wanted to design and build.

This became most apparent when rewriting the Dirty DigitalLFO code to use lookup tables. I will cover this AVR-C rewrite in a future post, but in short, maximizing my AVR embedded C code to produce a decent ramp wave via memory lookup had an output frequency that topped out at about 300hz. 

This was fine for an Low Frequency Oscillator. But if I wanted to design and build a decent DIwhy audio oscillator with an output frequency ranging from 20hz to 20khz, using only an AVR 8 bit, well, forget it.

The obvious thing to do was to get my hands on a faster processor--one that has a processor speed well above the 328's 16-20Mhz speed limit and its 4-5Mhz limit for SPI.  

My search came down to two processor families for future DiWhy adventures: the RP2040 and the STM32.  

Choosing between these two was a bit difficult. The RP2040 is more "hobby focused" and appears to be documented in a more user friendly way, especially for DIwhy newbies. It also has an extremely interesting feature--"PIO pins"; fascinating and 1001 uses! 

I need to discuss PIO with my urologist at some point but not now, OK? 

But for me RP2040's have a big problem: they are only available in QFN  SMD packages. This means the IC has no exposed pins for soldering since its solder pins are under the IC. Which makes fabricating custom RP2040 dev boards at home, using a hot air SMD solder process, difficult and/or impossible. I am not sure why RP made this choice, to make the chip fit on smaller boards, certainly? But its target audience is someone like me, building things at home best I can. So why, scotty, why? I won't bother with QFN, if it doesn't work, is it fab?  Bad code?  I won't know. Existing RP2040 development boards are affordable so if you want to go that route, you can. For now, not me.

For me--STM32 it is. 

There are plenty of affordable STM32 development boards on the market, ready for experimentation; I chose a Blackpill and a Nucleo F031K6. 


Nucleo F301JK6....

Both can be programmed using Arduino Sketch, MicroPython and other "maker" high level languages, and it's all easy--and thus what fun would that be? I will continue to use embedded C, and to further increase my glucose levels, and best understand the inner workings of ubiquitous ARM processors, will also code future projects using ARM assembly language.

Next question: what development platform(s) to use? 

The STM32 family is quite popular and there are many IDEs to choose from; I have gotten comfortable with the toolchain I've been using for AVR, but none of that works for STM32. 

After discussing with some pro EE's I know, I downloaded and installed free versions of STMcubeMX and Keil on my trusty lab NUC PC

OK time to set it all up. Where to start? The internet? "THE--INTERNET"  

A really good video series for getting started with STMcubeMX is by Mitch Davis, here. He has good AVR embedded C content as well! And--hear Mellow Count Dracula explain the CubeMX IDE setup here. Cool! 

Likewise, for Keil, check out the video here.  

Lots of new things to learn--for instance, DMA and I2S will be used extensively in future projects, and both these development boards support them, while Atmel 328's natively do not.   

Good news, after an evening's work, I can get embedded C blink and debug to work using the useful STM HAL library. Follow the excellent video here

 Let's next discuss ARM assembly.

But first--why an ARM processor? The good folks at didn't want to design a processor for their STM32 MCUs from scratch, so they did what many other tech manufacturers do--including Apple and Samsung--they licensed a suitable processor from ARM and built that into STM32's.

Other than writing 1's and 0's directly to the STM32's ARM processor--good luck with that--ARM assenbly programming is as low as I can go.   

As you might imagine, assembly is much tougher on modern ARM processors than say a 6502. But let's roll up our sleeves anyway.  

Are we mice or men?

I like to make tech things difficult, but I'm not a masochist, right? I figured a path of lesser resistance is to learn ARM assembly on a Raspberry Pi 4 Single Board Computer ("SBC").  The latter has the ability to display the assembly program's output to a monitor for instance--not at all easy with an embedded processor for an assembly beginner like me.

I bought a textbook from Amazon--"Raspberry PI Assembly Language Programming" by Stephen Smith. See the github here--even if you don't buy the book, its code examples are free....the book goes over every 1 and 0 for ARM assembly, it's extremely detailed, so I'll use that as a programming reference.

Next, I incorporated a Raspberry Pi4 into my lab as the target for ARM assembly language madness.  

Again, there were a lot of ways I could have set this up, and here is how I did it, and yes, i got "hello world" written in ARM assembly to work without having to leave my W10 development computer....the rest of this post explains the steps I took:

Raspberry Pi4's are cheap, reasonably fast, run the greatest DIwhy OS made (Debian, baby!), and thus there is no reason any AudioDiWhyer shouldn't have at least on on his or her bench.

The rest of this post also assumes you have a decent knowledge of Windows and Linux OS. There are so many videos and web pages about all of this I won't bother linking everything but if you get stuck let me know I will try to we go:

Get latest Rasberry PI OS--32 bit.   

Install on RPI4, make sure PI boots, etc etc etc.--a page for RPi setup is here.  

Install the xrdp  RDP server for RPI.  That way we can see graphics on a "headless" RPi4 computer from Windows:

sudo apt install xrdp

sudo adduser xrdp ssl-cert


test RDP:

from rpi terminal:

sudo ifconfig

and note IP in stdout.

from Windows 10, run mstsc (RDP client) using the IP you noted just above.  

You should see your xorg RPi4 login screen; login.  If it all works it will take you to your RPi desktop.  

So far, for me, this all works.

Next, we need to set a static IP address.

I set RPi to static IP to as local IP; gateway as, and used and for DNS servers; your IPs will be different):

sudo vi /etc/dhcpcd.conf

(change stuff you need to change)

and save your dhcpcd.conf file.  

Details about how top do this are here:

Reboot RPI:

sudo shutdown -h now

After reboot, test RDP from Windows again with the static IP.

For me, RDP still works!

Next: let's share saved data between Windows and RPi.

On windows system, run lusrmgr.msc ("loser manager")

  • create new local W10 user PI and add to admin group
  • share out folder(s) on C; 
  • give your PI local user full rights to share

MS post detailing how to set up CIFS shares and rights for Windows is here.

OK, next, finish getting your RPi4 talking CIFS with windows:

Go back to RPI

Run term:

cd /mnt

sudo mkdir audiodiwhy

run this command:

sudo mount.cifs // ./audiodiwhy -o user=pi,password=xxxx

where is the IPV4 address of your windows system and xxxx is pwd for local windows user you created above.  

Your folder names of course can be whatever you want.

next, cd to mnt/audiodiwhy:

cd /mnt/audiodiwhy

Do a directory listing:

ls -ltru

I can see the Windows folders from the mnt now. 

Next: Create a subfolder from RPi4 on your Windows system. Make sure your target windows folder has Linux read/write rights set; very permissive rights would be something like this:

sudo mkdir /mnt/audiowhy/write2here

sudo chmod 777 /mnt/audiodiwhy/write2here 

...and test everything by creating a text file, something like this:

sudo vim mnt/audiodiwhy/write2here/test

I can now see all the windows C data from the linux machine using RDP (by the way, SSH works as well, and might be preferable for some users, here is how to set up Rpi4 to allow it.  

This all works, we are writing new files to the windows system from the RPi4 using vim!

So you are RDPing or SSHing from W10 to RPI, then using CIFS to go back to Windows storage from Linux

Super COOL!!!!

(I mostly did this to add files etc to c:\users\audiodiwhy\dropbox since dropbox client isn't currently supported on ARM processors. But in general you can seamlessly save and open files on Windows from linux RPI using RDP in this manner--not 100% necessary, but very useful!)

Assembly time:

Now that I have my headless RPI4 working, I will create my assembly .s, .o, build etc., on the RPI Linux system but save them in dropbox windows (why not?),  that way everything lives in the cloud and is available whenever I have an Internet connection.

So on W10 system in c:\users\audiodiwhy\dropbox I created a folder /assembly/arm

Then I create these 2 files using the RPI gnome text editor --or vim or whatever you like, also, the code for these 2 files is available in  Chapter one of the github mentioned above (go here).  




@ Assembler program to print "Hello World!"

@ to stdout.


@ R0-R2 - parameters to linux function services

@ R7 - linux function number


.global _start @ Provide program starting address to linker

@ Setup the parameters to print hello world

@ and then call Linux to do it.

_start: mov R0, #1 @ 1 = StdOut

ldr R1, =helloworld @ string to print

mov R2, #13 @ length of our string

mov R7, #4 @ linux write system call

svc 0 @ Call linux to output the string

@ Setup the parameters to exit the program

@ and then call Linux to do it.

mov R0, #0 @ Use 0 return code

mov R7, #1 @ Service command code 1 terminates this program

svc 0 @ Call linux to terminate the program


helloworld: .ascii "Hello World!\n"


Now create another file called build


as -o HelloWorld.o HelloWorld.s

ld -o HelloWorld HelloWorld.o


from rpi term run this:

sudo ./build

this creates .o file, helloworld.o, objdump.txt etc as well as compiled HelloWorld we can run.

now let's run it!

sudo ./HelloWorld

STDOUT indeed shows HelloWorld on the screen--it all works!

OK, can we turn a C file into assembly, so we can see how the compiler is going to treat our c code? Of course!  Ctest.c is another helloworld....but this time written in C, not assembly.

sudo gcc -g -O -c ./ctest.c

That produces a .o file, which we can then dissassemble:

objdump -s -d ctest.o

Output is assembly:

00000000 <main>:

   0:   e92d4010        push    {r4, lr}

   4:   e59f0008        ldr     r0, [pc, #8]    ; 14 <main+0x14>

   8:   ebfffffe        bl      0 <puts>

   c:   e3a00000        mov     r0, #0

  10:   e8bd8010        pop     {r4, pc}

  14:   00000000        .word   0x00000000


More fun than a barrel of monkeys!!!  

OK that's enough for now.  Lots on the bench, I will continue with STM32 low level madness in future posts. Until next time:  Eat the PI, Don't breathe the ST(ea)M!

Dirty Digital LFO Rebuild: SMD Analog Board--Trouble Shooting Audio Op Amps

 Hello Again!  A few posts ago I got an LFO working based on Atmel 328P and some Embedded C Code . The module (post here ) works, but uses ...