Saturday, October 12, 2024

Building a Moog T904B Inspired High Pass Filter--Using an SMD hotplate--Works!

Readers: If you'd like to build the project featured in today's post, please go to PCBWAY's Community pages--gerber file (main board); gerber for jacks board, front panel gerber, KiCAD project/pcb/schematic/library files, a B.O.M. and more are here.  

You can also help out this site immensely by checking out PCBWAY using the link here. Thanks!

=====

This is a continuation of this previous post...where I laid out and built a Moog T904B HighPass Filter homage, based on a schematic from DIY pioneer Tom Gamble.  

Revision one's design/build attempt was disappointing, since it "worked" but sounded, well, terrible.

After rewatching the Kristian Blasol video (here) I wanted to keep working. The filter in his video sounds--interesting.  

Really interesting. Nasal, but also shimmery and a bit phase-shifty? 

To get this filter off DogDoo Island I redesigned two of REV1's PCB's, improving ground planes, since REV1's ground and audio traces looked terrible on my oscilloscope.

Good news: after building a REV2 Moog/EFM T904B, with PCB's provided by this blog's humble sponsor, PCBWAY, then spending an entire Sunday fixing stupid mistakes and experimenting with different component values, the Moog ladder highpass VCF sounds (I think)--much better. Maybe even good.

 I recall back in the day Mr. Gamble posting that his designs and kits were for experienced builders--something like that. In this case he was right! This HPF is a complex build, in terms of parts count, design complexity, and need for modification. This is not a good project for a beginner.  

AN ASIDE: WORDS FOR MY SPONSOR

Happiness is a new batch of boards from PCBWAY....

Shout out to Serene and the nice people at PCBWAY for sponsoring this blog. It sometimes takes me 2 or 3 revisions to get something to work; the T904B inspired HPF has been no exception. 

PCBWAY has always been patient, friendly and extremely supportive as I work through design changes. 

PCBWAY also has done great 3D printing for me and offers other useful services. They charge low prices and fabricate quickly. They are an asset to the DIY world; please help out this blog and check 'em out.

SMOKING HOT


What fun would DiWHY be without trying new (for me) fabrication techniques?  

For the Moog/EFM T904B VCF build I purchased a $39 "Amazon's choice" 100mm x 100mm hotplate and $10 worth of solder paste.  

Links for the plate and paste (hope these links still work):

 TLZBK 350W soldering Station Hot Plate

Wonderway SN42 lead free no clean solder paste

I got building--gooped the paste, not being too neat about it;

 



 I used tweezers to put the 1206 SMD parts on top of the goop, then dropped it on the hotplate.

Please work in a well-ventilated area. This hotplate/paste/SMD process produced a lot of toxic smoke, and you shouldn't breathe its fumes.

Baked the jacks board....


Marvey--what temperature to use? 

This chart shows the heat curve one should follow-"IPC/JEDEC J-STD-020C impressive right?--but the plate only had set/enter switches for temperature; inputting complex functions for heating/cooling wasn't possible.


Instead I decided to heat the damn thing up, and after the parts were put in place and the solder had melted, cool things back down. 

To my surprise this approach worked. At about 195C the parts magically centered themselves to solder pads and the solder flowed where it needed to go. 

After the parts were set I let the plate continue to heat up to about 210C, then told the plate to climb back down to 100C, which it did, slowly.  

At about 150C I removed the PCB.

Visually inspecting: in spite of my heat curve being far from ideal, it worked! 

The results were good: 


 

"Seems working." I was chicken at first to bake on an SOIC TL071, but successfully soldered 2x TL074's using the hotplate when I soldered the main PCB's SMD parts.

I repeated this for a few more boards.  

Overall, the process was far more forgiving than anticipated. There was a solder bridge on an SOIC TL074 that I fixed with solder wick, and one 1206 resistor baked on 90 degrees opposed to where it should have, which I corrected with a soldering iron and some tweezers. 

Otherwise--all good.

One byproduct--after the 1206 bake there was a lot of leftover flux on the PCB. I used "no clean"paste, meaning this leftover flux isn't conductive and can stay where it is, but I dislike spoojim all over my PCBs. I am still trying to figure out how to best clean the no clean: flux remover, isopropyl alcohol, and other attempts haven't worked...maybe I have to leave it.

Overall using the hot plate and paste was far easier and less time consuming than soldering 1206 SMD parts by hand, and I imagine with a solder mask difficult SMD parts (QFN's?) are doable for DiWHYers using this inexpensive setup.

I will keep working with this hot plate for upcoming posts.

HIGH PASS DRIFTER

OK, let's talk HP filter.

The original Moog patent design looks like this:




From this MW forum post  the EFM design resembles the Moog ladder filter patent...indeed:

Long Live Moog! Long live EFM! Interesting: this is a 24db/octave high pass filter, a lot of other popular audio HPF's are 12db/octave (the Oberheim SEM's for instance--a favorite); meaning the T403B can be driven to oscillate.


From here it was a VCF build like so many other VCF builds: design and revise PCB's with Kicad; send gerbers off to PCBWAY; solder SMD's first then add through-hole components...

As per a comment in the youtube video: I didn't have to match transistor Vbe's, so I didn't.

Before too long I had new PCB's, populated and ready to test.

Testing. I used an single 3mm nylon phillips screw, standoffs, and nut to secure the jacks and mainboard to each other.

Did it work first time (WFT)?  

Nope.

I spent a masochiastally joyful Sunday (all day pain, all day long) troubleshooting the damn REV2 build. 

There were a few stupid build errors, the biggest: I put .22uF caps in the resonance feedback subcircuit, where .22pF should be used. DOH!  

I had a cold solder joint in the 100mil V-- trace between main and jacks board--that didn't help.

EFM boo-boo? The EFM schematic showed the main frequency cutoff pot with V++ (I used +12) Vdc on one side and ground on the other. Nope. It needed V+ on one side and V- on the other--this is corrected on the PCB's uploaded to the PCBWAY community site:


Otherwise the VCF required CV values below ground....doable, but not "normal" as I see things.

I found the EFM resistor values for CV mod didn't work for me; for my rack's 0-5V CV setup I used 20K's and B50K pots as in the snippet below....choosing appropriate values for R1 and R2 was necessary to get the VCF to sound decent.

 

Fortunately incorporating 1206 SMD resistors made trying different values for R1/R2 easy. 

Using a 700F soldering iron with a sharply pointed tip I heated up both ends of the tiny resistors, removed them, and soldered in something different. This was faster and easier than working with through hole resistors and posed less danger of damaging traces when changing out components.

MODS AND SODS

Besides messing with resistor values I made two semi-major modifications to the EFM schematic:

First to the jacks board I added an output buffer:

R6 adjusts output and in some cases (oddly) the resonance quality--try between 10K and 100K to get the sound and level you want; I started with 10K (shown) for R6 but later changed it to 50K.
 
Second: I added an audio input attenuator to the jacks board: 

 
I added the latter because in this cool Youtube video the filter sounded slightly distorted at times.

Looking at the original schematic I guessed that high peak to peak input signals saturated the first stage of the PNP/NPN/op amp trifecta--just a guess.

To my surprise the additional input attenuator didn't only let me dial in appropriate audio levels to remediate distortion, in some cases, the attenuator changed the character of the filter's resonance. Not what I expected, but I'll take it!
 
One more tidbit: I didn't have 220K 1206 resistors in my junk box, so on the main board I used 200K for R11 and 30K for R21. I am not sure how common 220K 1206 SMD resistors are at retailers like Tayda--whatever.

IMPROVED OUTRO

Anything Moog and EFM are cool right?  

Sure, but: the EFM Moog HPF isn't a filter where every setting sounds great. In general I found I had to experiment--it's an odd module--messing around is a big part of what we do, so this is a feature, not a bug? 

If I were to continue working on this filter--not sure I have time--I'd add a resistor in series with the V++ feed to the cutoff1 pot (current "jacks board" has a resistor for V--, but not V++).  

Why: a lot of potentiometer real estate for the frequency cutoff pot about above 2PM was useless--turn the frequency pot above 70% and the audio at output was gone.   

The modification to the cutoff pot should look something like this:



Update: modified! I cut the "3" leg to Cutoff1 on the jacks board and kludged in a 12K THT resistor between it and the V++ rail. Big help! If I do a REV3 of the jacks board I will include this mod.

Also: since this is a highpass filter, it might be useful to change one of the modulation inputs to invert the incoming CV signal and set its bias to somewhere above ground--2.5V perhaps.  Or, even better: add a modulation attenuverter to get cool inverted highpass effects. 

OK enough for now--this has been a maddingly fun project--to remain high, consider giving this filter a pass.  Or something like that?  

Sunday, September 15, 2024

4 x 1 Mono Mixer--A Confidence Builder

Readers: If you'd like to build the project featured in today's post, please go to PCBWAY's Community pages--gerber file (main board); gerber for jacks board, front panel gerber, a 3D model of the dual pot (WRL and Freecad formats), KiCAD project/pcb/schematic/library files and a B.O.M. are here.  

You can also help out this site immensely by checking out PCBWAY using the link here. Thanks!

------

I've DIWHY'd many projects lately that don't work due to dumb mistakes and I thought, enough! I'm going to lay out and build something easy, useful and practical. 








Everyone needs more mixers right?

The "EZ MIX" was really easy; it took me an evening to lay out and about a half hour to build: a simple 4 input AC/DC unity gain active mixer in 6HP Eurorack format.





The star of the show: dual concentric pots; you get Eurorack's "fit every damn thing on the head of a pin" paradigm without needing extremely tiny hands to turn the knobs.

           

                              

I laid out a footprint for the concentric potentiometer in Kicad and 3D model in Freecad:  



                                                 

Piece of cake.

The project used 3 boards: front panel, jacks panel, and main PCB, centered around a single dual op amp IC.

Everything on the main board was through-hole; other than triple checking sizes, legends, and drills there wasn't a lot to go wrong. 

To remain old school: JST connectors were used between the main board and jacks board.

Main board

jacks board

Front panel....

Off it all went to this blog's honorable sponsor, PCBWAY....

Happiness: new boards from PCBWAY

Zoom, printed circuit boards were back, I gathered up parts from the junk box and got building.






I was fearful things wouldn't line up, thankfully they did....






Everything worked first time ("WFT")--pots went from no volume to full volume when turning them clockwise, audio signals got mixed, legends on the front panel were correct, and most all silkscreens on the boards were accurate.

One (big) mistake--the "Redstripe" (negative voltage) silkscreen callout was on the wrong end of the Euro power header. This silkscreen error was fixed on the 9-12-24 revision I uploaded to the PCBWAY community.

The only SMD part for the project: I added a 0 ohm 1206 resistor between Out1 and Out2. That way if I reused the 3.5mm jacks board in another project the 2 outputs could be independent of one another.  

But in the spirit of this build I soldered the 2 output wirepads together--who needs SMD?



 Finished!






So much fun I made two of them...

Overall, it was great to kick back and design then build something really easy. 

Getting stoopid from time to time is fun....but now it's back to debugging, dealing with cryptic IDE error messages, C++ that won't compile--all the usual things. I can't wait. 


Saturday, September 7, 2024

Understanding ARM Exceptions/Interrupts

Readers: For upcoming posts--low frequency counter, clock multiplication module, etc., I will dig deeply into ARM processor exceptions, a.k.a. "interrupts."

Exceptions can be complex in the ARM world, requiring mastery of hardware, peripherals, programming concepts, and baffling acronyms (e.g.: "ICSR","AIRCR" "NVIC" etc).

This post captures my lab notes surrounding ARM exceptions.

I will be updating/correcting/augmenting this post going forward as I work at the bench. 

Which means: this post is mostly for me--if you want to skip it, skip it. 

but! if you correct things via the comment section I would be grateful--help from experienced ARM developers is always apprecated. 

Thanks.



LINKS

Good web page for this: https://interrupt.memfault.com/blog/arm-cortex-m-exceptions-and-nvic

Video: https://www.youtube.com/watch?v=Uut2uIODlbQ&t=305s; sadly there is no source code linked--so, useful to RP2040 dev until about 8:00.

SysTick: https://www.youtube.com/watch?v=fMqCBMWZ0pk ARM Programming courses frequently use SysTick exception 15 to demostrate interrupts. SysTick is a relatively simple counter that can throw an exception over and over. The video explains how SysTick is implemented in an ARM core but does not provide code examples one can download.

C code example for Systick: https://users.ece.utexas.edu/~valvano/arm/SysTick.c.  SysTick code for PICO is here. Other forum posts indicate that the PICO SDK does not abstract SysTick functionality, hence, you have to set the registers directly:

#include <stdio.h> #include "pico/stdlib.h" #include "hardware/structs/systick.h" int main() { stdio_init_all(); systick_hw->csr = 0x5; systick_hw->rvr = 0x00FFFFFF;


PDF of the "generic Cortex Users' guide" is here--how an ARM Cortex M core works at a deep level. Section 2.3 covers a lot of what is in this post; section 4.1 covers peripherals such as NVIC (below). I find this PDF more useful than the web based version here.

---------

I hear ARM exceptions referred to as “interrupts” but in the ARM world interrupts are a type of exception.  

Exceptions have a unique number associated with them starting at 1 (not 0!)  

This number is then used as an offset in a vector table; at each offset is a memory address of a routine to be run.

A made-up example:

If the ARM core sees exception 1 run a routine beginning at 0x0002534

If the ARM core sees exception 2 run a routine beginning at 0x0002538

And so on.

Along with this unique number is “priority”.  The idea here: if 2 exceptions are recognized by the processor at the same time, which one gets handled first?  The lower the priority #, the higher the "precedence"—the processor handles the lower priority #’d exception first.

 

ISR

INTERRUPT SERVICE ROUTINE (ISR): the routine that is pointed to by the vector.   


STATES

Exceptions in the ARM world have "STATES":

Pending: the processor has seen the exception but hasn’t done anything yet. Important idea: for an exception to be recognized and eventually run, a 1 is put into a register to make the exception “pending”, then based on other factors the ARM core will process it. See REGISTER section below for details.

Active: the exception handler is running but isn’t finished yet.

Pending and active: the processor sees the same exception that it’s currently processing.

Inactive: an exception is neither pending nor active.

 

ARM EXCEPTIONS--"CORE INTERRUPTS"

These are exceptions hard coded into an ARM Cortex M core.  

Built in exceptions are used for housekeeping--you can’t change them.

The NVIC (below) peripheral is not used when processing these particular exceptions (note, is that right? I read different things about this, so I am not sure--are both core and non-core exceptions handled by the NVIC, or not? Does anyone know for sure?).

0 vector table points to reset value of stack pointer.

Reset: 1. The routine that is executed when the chip comes out of reset.

Non Maskable Interrupt or "NMI"--2: this exception cannot be disabled. It is fired when there is an error in another exception handler.  The vector points to a routine to run in this oh-shit scenario.

Hardfault: 3  Routine is run when there is a major fault: memory error, divide by 0, etc.

MemManage: 4

Busfault: 5

7-10 reserved

SVCALL: 11:  routine to handle calls to handle a service routine—for instance, a call to the OS to do something.

Debug monitor: 12

13 reserved

PendSV: 14

SysTick: 15. See below.


EXTERNAL or "NON-CORE EXCEPTIONS" 16+0 to 16+N 

These interrupts are defined by the SOC developer; they vary from chip to chip....

The Cortex M0+ (RP2040) has 32 non-core exceptions; other ARM chips can have more.

IRQ's are the external exception numbers minus 16, so exception 0x0F is IRQ 0x0, 0x10 is IRQ 0x1, etc. 

REGISTERS

Exception state is captured in registers located in the ARM System Control Space ("SCS")

Interrupt Control and State Register, ICSR

If you want to check up on what the processor is doing or is going to do exception-wise look at this register.  The active/inactive/pending/and "what exception is running now" is stored here.

Application Interrupt and reset Control Register (AIRCR)

used to further prioritize your exceptions.

Also--Writing a 1 to bit 2 fires a system reset.

System handler Priority Register:  SHPR1-3   

Used to set priorities of system faults.   You probably will never change this register.

Interrupt Controller Type Register (ICTR) - 

In cortex M0+ (RP2040) it's always set to all 1’s.  Don’t worry about this one.

 

NVIC REGISTERS

NVIC is a peripheral that handles manufacturer-specific exceptions; NVIC supports up to 496 exceptions; the Cortex M0/M0+ is limited to 32 external exceptions. 

Example: if your UART is throwing an exception when its buffer is full, it's utilizing the NVIC registers to inform the processor of this exception.

Interrupt Set-Enable (NVIC_ISER) and Clear-Enable (NVIC_ICER) Registers

  • NVIC_ISPR0-NVIC_ISPR15: << set pending
  • NVIC_ICPR0-NVIC_ICPR15:   << clear pending

Writing 1 to the correct register memory location will set or clear the pending state of the interrupt.  So—you can make an external interrupt pending, telling the processor to deal with it; or set an already pending to 0—telling the processor: nevermind. On Cortex M0+, this is the main way one sets external exceptions—drop bits into the above registers.

 These “Hello! CPU here: thanks for telling me about this exception, I will deal with this exception soon enough” situations are referred to as asynchronous exceptions.  

There are also synchronous exceptions (SVC is one, I read), but I am going to not worry about those right now.

 

Interrupt Active Bit Registers (NVIC_IABR)

Not implemented in M0 (RP2040).  Don’t worry about it.

 

Software Triggered Interrupt Register (STIR)  

Also not implemented on M0. Don’t worry about it.

 

Interrupt Priority Registers (NVIC_IPR): NVIC_IPR0-NVIC_IPR1-2-3:

Sets interrupt priority.

 

Software Triggered Interrupt Register (STIR)  

Also not implemented on M0. Don’t worry about it.

 

HOW ARM CHIPS DEAL WITH EXCEPTIONS FROM A C COMPILER STANDPOINT

The compiler is set up to conform to Arm Architecture Procedural Calling Standard (AAPCS).

It works like this (oversimplified?):

a)      An exception is called

b)      Caller register values are put on the stack

c)       $lr (link register) gets the EXC_RETURN value—where in the program to go after the exception is handled.

d)      After exception is handled the values are popped back off the stack, the program returns to $lr value and keeps executing. 

      This means the push and pop complexities associated with implementing an exception without clobbering the rest of your code is abstracted by the compiler. That's probably a good thing?

"TAIL CHAINING":

Say we are executing an ISR and another one comes in.  The processor will not redo the caller stack pushes and pulls, it will leave whatever is in the stack, in the stack. This can save CPU cycles.

Sunday, August 25, 2024

Debugging Raspberry Pi PICO with a Segger J-Link EDU Mini

In the previous post here I set up a $4 "Picoprobe" to debug RP2040 C/C++ code running on a PICO development board. 

This worked OK but I found it a bit unwieldy at times; the debugger was often physically larger than whatever I was debugging. 

Could I get a smaller debugger into play?

A tech friend told me about the "EDU" version of popular Segger J-LINK debuggers; I read online that Segger had made a "mini" version of it. 

Segger's J-LINK EDU MINI debugger. Yes, it's mini.



I emailed them; their sales team got back to me quickly. 

This is a USD $60 purchase for non-commercial use; even though it's for education ("EDU") you don't have to present a student ID. 

But you also can't make money off your code.....all audioDiWHY code is MIT open source so I figured: I qualified.

The Segger page here made me think the adapter would work for debugging RP2040's. For what this debugger can do and all the free Segger software it came with it seemed like a hell of a bargain. 

I bought one.

WIRING IT UP

It's tiny! Its JTAG/SWD interface uses 2x5 pin 50 mil pin headers--1.27mm --extremely small--conforming to the "Coresight 10" JTAG pin specification.

The unit comes with a 10 pin to 10 pin IDC ribbon which won't work with the PICO; the PICO has SWD pins that are 100mils apart. 

I had a JTAG 50 mil to dupont breakout I got with my Atmel Ice programmer; this breakout was unused, so I employed that; you will need something similar, try the link here.

OK, time to wire it up. I had to dig around on the Internet re: how to connect the EDU mini to a Pico Dev board and eventually figured it out:

 An SWD breakout is coming soon from this blog's helpful sponsor: PCBWAY.

I wired it up on a breadboard:


Unexpected:  "pro" Segger J-Links have a 5V output for powering your development board, but the EDU mini requires a 5V reference and cannot source 5V.

So: I used a bench 5V power supply for this.

It was time to check my wiring. On my windows system I installed the Segger tools (here) and ran their Commander utility  to verify all the wiring was sound--it was.

DEBUG SOFTWARE

From the previous RP2040 debugger post I knew I needed a version of GDB for the Segger J-link, since GDP for embedded seems to be hardware dependent; fortunately Segger's GDB was available as a free download; here.

OK, the next problem: how to set up this modified debug toolchain without going crazy. 

After some research: I would continue to use Ubuntu VSCODE (as opposed to a different IDE) as the path of least resistance.

Note:The previous post on the initial Ubuntu/VSCODE toolchain setup is here. If you are setting up Segger/GDB/VSCODE for RP2040 "from scratch" you may want to read it or find advice elsewhere on the Internet.....you will need the same extensions for VSCODE installed for the Segger debugger to work as PicoProbe: C/C++ and Marus25's Cortex Debug, along with the arm-non-eabi compiler and so on.

I installed Segger's GDB code on Linux (I use an Ubuntu virtual machine) which ended up in /opt/SEGGER.

Now came the tricky part: getting VSCODE's dreaded launch.json file configured use Seggar's version of GDB.

Here is the launch.json I ended up using; this got copied into my project folder's .vscode folder after renaming the existing picoprobe launch.json file to launch.json.old.

{

"version": "0.2.0",

    "showDevDebugOutput": "raw",

    "configurations": [

     {

        "name": "J-Link",

        "cwd": "${workspaceRoot}",

        //"gdbPath": "gdb-multiarch",

        "gdbPath" : "/usr/bin/arm-none-eabi-gdb",

        "executable": "${command:cmake.launchTargetPath}",

        "servertype": "jlink",

        "serverpath": "/opt/SEGGER/JLink/JLinkGDBServer",

        "armToolchainPath": "/usr/bin/",

        "request": "launch",

        "type": "cortex-debug",

        "interface": "swd",

        "serialNumber": "801056408", //yours won't be

        "device": "RP2040_M0_0",

        "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd",

        "postRestartCommands": [

          "break main",

          "continue"

        ]

      }

    ]    

}

In VSCODE, on the bottom right, I had to set the file type for launch.json from "commented JSON" to
"plain text"....then throw away the previous build file and finally, recompile my stupid blink main.c and push the UF2 file to the Pico.

It worked! 

With this new debugger in play: I can see registry contents, set breakpoints, watch variables, all the usual debug stuff, without having to leave VSCODE. 

And--I got it to work in about 4 hours, start to finish, on single Sunday--surprising even myself. 

SIDENOTE: RTT ADD-IN--FAST PRINTF()

GDB is cool, but arguably nothing in the debug world is easier and cheatee-er than a C printf() statement to reveal a variable value or whatever.

Come on, we all do it right?

Problem with printf(), the version in the RP2040 stdlib anyway: it's pretty MPU hungry and can jam up other I/O while sending strings to the terminal.

SEGGER provides a freely available, more efficient printf() algorithm that's easy to throw into your project. 

Here's how I set that up:

  • Downloaded the tools for debian from the "J-Link Software and Documentation Pack", here
  • Right clicked on the .deb file, said "use other software" and ran the .deb installer.
  • The files I needed got installed in /opt/SEGGER.
  • Gunzipped/unpacked the files in /opt/SEGGER/JLink_[version]/Samples/RTT;
  • I needed SEGGER_RTT.c, SEGGER_RTT.h, SEGGER_RTT_Conf.h, and SEGGER_RTT_printf.c....
  • I copied those files into my RP2040 project.

Next I modified CMakeLists.txt and .c files to get the RTT printf() statement to work. 

Please go to Github (here) and look at the example/proof of concept files, including an example CMakeLists.txt; I think you will find it pretty easy to get an RTT printf() statements into your projects once you see the examples....in the repo--main() is in the file blinkcl.c....

OK with the sample code loaded into a Raspberry Pi Pico:

  •  I hooked up the J-Link Debugger using the wiring diagram above.
  • Went back back to opt/SEGGER and opened JLinkRTTViewerExe:


With the RTT viewer open I needed to connect the RP2040, so I went to to file > connect and clicked the three dots (...)


 and choose the RP2040 Core 0 from the drop down, finally, clicked OK.


When I powered up the PICO the RTT print statements showed up in the RTT viewer:


RTT has a bunch of other cool features--printf() is only a small part of what it can do--more information here.

OUTTRO

Overall this was a good addition to my bench. Getting it going was much smoother sailing than other debug setups to date. 

For $60 the Segger J-Link EU MINI will become an oft-used tool on my bench. 

In future posts I will build/post a simple JTAG to SWD breakout board, which will hopefully make quick set up of this or other SWD debuggers.  Until then: don't breathe the fumes.







Saturday, August 17, 2024

Debouncing a Rotary Encoder Revisited

Update 9-29-24: Be warned, I have built a few of these boards now and some of them work and some don't. Not a fab issue, a design issue.

I will revise the ground pours etc., I think this is a layout issue.

 If you want to build the project featured in today's post anyway, please go to PCBWAY's Community pages--gerber file (main board) ready to download and/or fabricate, KiCAD pro/pcb/schem files and a BOM are here.  

You can also help out this site immensely by checking out PCBWAY using the link here. Thanks!

----------------------


Rotary encoder debouncing is alive and well: here, here and here for instance.

This post I try out an interesting debounce algorithm, silencing my rotary encoder's chattering teeth once and for all, using a small bespoke development board centered around the Raspberry Pi Pico and its RP2040 MCU.

Good news: the debounce algorithm works great.  

 


 
This started when DIY mensch Christer Janson emailed me, postulating a way to do encoder debounce using pin state as opposed to more the traditional methods: using hardware (example here) or waiting a few milliseconds after initial contact to let the chatter subside (here). 

I was baffled, his idea seemed too far out.

A few days later Christer emailed me again with a link to Marco Pinterec's website where this unfamiliar debounce paradigm is discussed. 

Ralph Bacon's video (here) shows Marco's idea in action.

"Sit on it and rotary"



To (over)simplify: the algo reads the encoder's state once pins have settled into a physically possible condition and only then outputs value indicating that the encoder is incrementing or decrementing.

Christer took the code from Marco's site and turned it into C++ classes. One class can be used when an interrupt is fired ("CCJISREncoder"), also a inherited class for polling encoder(s) using an infinite main()  loop ("CCJPollEncoder").  

Cool! 

Get the code from Christer's website here.

You can get my Raspberry Pi RP2040 port of the polling algo on Github, here; the ISR version with a main.c example is here.

GETTING IT TO WORK....

This was a simple one right?

Not at first. 

Before working on debouncing of any kind I strugged for 3+ evenings (!!) getting the Pico's serial TX pin 0 connected to an Ubuntu Linux PC running Minicom to display output. 

I have been using serial UART with Pico dev boards, SBC's, and ATMega328's since about 2019 without issue.
 
For this project I cracked open a batch of new PICOs; 4 boards total. 

I found that one new PICO could output a 112K serial stream for a few seconds before the stream stopped, while the other three didn't output serial at.  

Hello???? 

Of course I was using "hello snotrocket" code to test the output; I knew the code worked, having uploaded and tested it on earlier Picos without issue.

I tried everything--in the most logical manner I could--different computers, different USB ports, different GPIO pins, different USB cables, a CH340 based clone instead of an FTDI for serial to USB, all to isolate what worked and what didn't; no luck. 

It had to be the new batch of Picos!

Pico of crap? Usually not. But four from Amazon had a problem with breakfast carbs: i.e., serious cereal issues.




Adafruit's FTDI F(r)iend! converts RX/TX serial to USB.  Easy to use, well documented; works on Linux OS without extra drivers. Highly recommended, but only if the board you hook it has a working serial TX output, dammit.


After a lot of Internet research, scribbling notes, more Internet research, looking at scope output, reading many SDK pages, and still more Internet research, I guessed that I had some sort of pullup or pulldown issue, not needed in all my previous Pico dev boards, but somehow, for these four new ones, necessary.

Mind you, this was a guess.

I put a 10K resistor between TX and ground--still broken.  

But to my amazement: when I put the 10K from TX to ground--FIXED.  For all four!

Speculation: my older Picos had different component tolerances regarding output impedance, so they worked without the 10K pulldown, but this newest batch, nope--pull down resistors for TX were obviously needed.

Oddly, I couldn't find anyone else on the Internet with the same issue. 

Maybe most DiWHYers use the USB cable based Arduino terminal window instead of GPIO pins for serial, so, if it didn't work, well, no one cared.

Maybe I got a few lemons from Amazon and no one else did?   

I will never know. 

Anyway...I soldered a 10K resistor between TX and ground. I incorporated this mod into the gerber I uploaded to the PCBWAY community site. If you build this board and are using serial pin GPIO 0 (TX) for output, you might want to use a 10K 1206 resistor for R6.  

A potential solution and it ain't pretty.


I figured some sort of pulldown method in code could also be put to the test but that's for another time.

To get to the bottom of the pulldown issue I recreated the design (adding an OLED) on a breadboard; in this configuration, no pulldown resistors were needed.



THE CCJ ALGORITHM

Christer's code demonstrates his deep knowledge of C++, using protected classes, virtual functions, inheritance, and more.  

It was written for AVR/Arduino so I had to change things like "digitalRead()" to C SDK functions for the RP2040. 

How to use it?....if you are calling the algo from an infinite "while" loop there is not a lot more than this:

CCJPollEncoder encoder1; //instantiate


int main() {
    
   stdio_init_all(); //initialize printf
   encoder1.init(10,15); //initialize GPIO pins
 

//In a main{} infinite loop:

            if (encoder1.check())  // we call CCJ class method
            {
               int a = encoder1.value();     
               printf("Value is: %d \n",a);  // print out inc/dec                                                 //value via serial
            }

For Christer's "CCJPollEncoder" library I initialized two GPIO pins and ran two function calls; when check() returned a true, value() picked up an incremented or decremented value based on the rotary encoder's state. 

But, with serial I/O working again, could I make it go? 




At first I saw nothing, no serial data, no increment, no decrement, even when the PICO could stream "hello wheaties" all day long running other firmware.

I reached out to Christer for help. 

He reviewed my main.cpp file and suggested removing gratuitous LED flashes I put into main() since they might be clobbering the serial stream.
  
He emailed me this clever "only flash your LED when nothing else is going on" snippet to use instead:

//before main

uint64_t blinkTime = 300000;
bool ledState = 0;

//inside main()

absolute_time_t lastBlink = get_absolute_time();

while (2) {
        absolute_time_t curTime = get_absolute_time(); // current                                                             //time
        // Check time since last blink, if time since last blink is          //larger than blinkTime, then blink.
        if ((absolute_time_diff_us(lastBlink, curTime)) > blinkTime)
        {
            lastBlink = get_absolute_time(); // update blink time
            ledState = 1-ledState;                 // Flip ledState                                                     //between 0 and 1
            gpio_put(LED_PIN, ledState);    // update led

(Wow! This is cool. A million and one uses--Telling you: Christer has the skills to pay the bills.  ).

With Christer's improved LED flash mini-algorithm the CCJEncoder class worked immedately and worked great.  

I could see the rotary encoder's output increment (CW) and decrement (CCW).  

And best: the encoder debounce was--perfect.  

I mean Abso-frigging--lootly--perfect. Never a false positive. never an issue.  Never a missed beat. Perfect!!!

update 8-24-24: I ported Christer's "ISR" class to RP2040; tested it working as well. If you want to use RP2040 GPIO interrupts to grab encoder data, vs. infinitely polling, you'll want this version--get the C++ .h file and example main.c at github: here.
 


"seems working"

 

BUILDING A DEV BOARD FOR YER PICO DEV BOARD

If you follow this blog you know I hate breadboarding, so I moved the project to a printed circuit board, adding functionality to the PICO, fabricated by my ever patient and supportive sponsor, PCBWAY.  

 
Shout out to PCBWAY for sponsoring this blog. If you have PCB, 3D printing, metalworking, you name it, needs, please check 'em out.





Populated--working!


OUTTRO


There is a long way to go with this one...I want to code a simple encoder driven user interface for the OLED for the ongoing RP2040 frequency reader project.  

All of this in the middle of busy times at my day job...but I will get it done right?

"burned up sir"

One more tidbit before going back to work...in the midst of my issues with unreliable serial ports I took no mercy on one of the 4 misbehaving boards.

I got out my rework station, melted the crap out of a PICO, then literally threw the tiny thing out the window.  

This didn't make me more productive, but it felt pretty good. 

I had a tech boss long ago who liked to say: "anything can be fixed". 

Sorry, not this time.

Building a Moog T904B Inspired High Pass Filter--Using an SMD hotplate--Works!

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