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 indeed 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 that came with it 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, which 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 used 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 is easier and cheaty-er in the debug world 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....
  • So 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, I think you will find it pretty easy to get an RTT printf() statements into your projects once you see the examples.

main() is in the file blinkcl.c....

OK with that done:

  •  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 and much smoother sailing than other debug setups to date. For $60 the Segger J-Link EU MINI will become an often used tool on my bench.  Next up, I will build a small JTAG to SWD breakout board, which will hopefully make setting up the debugger quicker.







Saturday, August 17, 2024

Debouncing a Rotary Encoder Revisited

Readers: If you want to build the project featured in today's post, 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.

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.

Sunday, August 11, 2024

CPULATOR--Stop Showering, Learn ARM ASM

 Let's start this one with a snipping tool PNG of the ubiquitous Arduino Nano:

Let's say I want to blink its LED in an extremely laborious manner.....

I could craft a really long string of 1's and 0's and somehow feed this into the Nano's memory.

If I got everything just right the Nano's on-board LED would---well--blink

Of course sequencing the correct the 1's and 0's, getting them into the nano's flash memory, troubleshooting things when the LED just sat there, everything else? would take days, weeks, who knows.



Of course there are easier, highly abstracted ways to do this. 

The popular one: write a few lines of code using Arduino's handy Sketch language, hit "compile and upload" on your PC, optionally wire up a breadboard: se habla blink. 

Weeks? Nope. Minutes. 

Sketch allows us to impress our colleagues, puzzle our spouses, and, as tech nerds, in record time, get on with our lives.  ASM? Not as much.

However I stopped using sketch many years ago....too easy. I'm into tech torture. 

Assembly language is about as low as you can go without having to calculate actual sequences of 1's and 0's.  

This time I document my first steps learning assembly programming for ARM processors, the instruction set used in so many popular MCU's, smartphones, personal computers, and every other damn thing. 

The platform of choice for learning ARM ASM, for me, was the amazing CPULATOR

The different SIMS CPULATOR has to offer. Free! Amazing.


SEE-PHEW LATOR?

This is an awesome online learning tool. Code, compile, and debug NIOS, ARM, MIPS, or RISC-V assembly. The simulator has virtual peripherals like 7 segment LED's and switches, also a code editor, a disassembler, and a searchable memory map.

It's browser based--so, nothing to install or download.

Overall: if you want a challenge, and you want to really learn how computers work at a deep level, ASM could be hugely benficial.

With that out of the way.....

Let's CODE!

There are plenty of good resources for learning ARM ASM: tutorial video series that start here and here for instance.

I found the ARM ASM documentation (here) somewhat hard to follow but discovered I could google ASM instructions like ADD or MOV and usually get what I needed pretty fast: explanations of what the instructions did and code examples.

I also found a number of "cheat sheets", the one here is good for instance. To decipher however you need a cheat sheet for this cheat sheet:
  • Rd,Rt:  a destination or target register
  • Rn, Rm,Rs:   source registers
  • "instruction":  MOV, ADD, STR -- the instructions, Elmo.
  • "Operand": data in a register 
  • <Operand2>  go to page 5 of the cheatsheet for details
  • <amode2/amode3/amode4 etc>  go to page 5 for details
  • {cond} if/then based on a condition (for instance, if a CSPR flag is set): Go to page 5 for details.

I posted whatever fragments I got to work on github--here.  

For the rest of this post I will focus on a somewhat complex (for me anyway) algorithm I wrote for CPULATOR. 

It takes 6 least significant bytes in an .equ variable (which could pretty easily be put into a register) and puts them into the sim's 6 digit fake 7 seg displays:




 
You can get the code for this here.  Let's dig in.
 

First, how does the memory map for this work--The 7 seg is marked 0xff200020:




That's the memory mapped location to address the rightmost 7 segment display, but, what about the remaining leftmost 7 segment displays that appear to the left of it?

Turns out I had to left shift bits (LSL) by 8 to have them occupy LED's to the right of xff200020, then add any subsequent data to that, finally, STR it:

       LDR R2,[R0] @load value found at mem_0[0] into R2

       LSL R2,#8   @now, offset this value by 8 positions
 
       LDR R3,[R0,#4] @get mem_0[1] and put in into R3
   
       ADD R2,R2,R3  @add value of mem_0[1] to what is already in                           @R2,the LSL's mem_0[0]
      STR R2,[R1]   @finally store this puppy into 7seg 


Makes sense, but I also incorrectly guessed that STR'ing to 0xff200028 would get me to another 7 seg.  Nope.

After the 4 rightmost 7 segment LED's we are out of bit shifts--this is a 32 bit CPU so we can't shift beyond 8*4.  

The rightmost 2 start at 0xff200030. 

To get to the 7 seg furthest left, LSL and add. Same idea as above, but from a different starting point in mapped memory.

It seems the idea of getting a value, putting it into a register or memory then shifting it, repeating with a lesser shift, then adding, is pretty common in low level programming.  For example I found the video here from the great "Core Dumped" YouTube channel outling a somewhat similar idea used for casting strings to integers. 
 
OK, we can address each 7 segment display, but writing a hex integer to them looks like crap. Of course, this is a 7 segment display, with different bits lighting up different LED's, so simply writing hex values 0x0-0xF directly will come out fugly:

The 7 seg may look bad, but boards you get from this blog's sponsor, PCBWAY, never will.  

   
Therefore I had to write an algorithm inside the algorithm to convert hex to appropriate values for the 7 seg displays.

Some of the steps needed:

Create an array:
 segs: .word ZERO,ONE,TWO,THREE,FOUR,FIVE,SIX,SEVEN,EIGHT,NINE,XA,XB,XC,XD,XE,XF

Create a series of variables to populate the array:
 .equ ZERO, 0x3F
.equ ONE, 0x06
.equ TWO, 0x5B
.equ THREE,0x4F
.equ FOUR,0x66
.equ FIVE,0x6D
.equ SIX,0x7D
.equ SEVEN,0x07
.equ EIGHT,0x7F
.equ NINE,0x67
.equ XA,0x77
.equ XB,0x7C
.equ XC,0x39
.equ XD,0x5E
.equ XE,0x79
.equ XF,0x71

Put the array into memory--the address of which is stored in Reg 2:
LDR R2,=segs @memory array of values for 7 seg conversion

Translate the hex value to the correct 7 segment equivalent by  walking the array for a match (this is not the whole subroutine, but you hopefully get the idea):

   MUL R3,R3,R4     @we need to multiply value of counter R3                              @by 4.
   LDR R0,[R2,R3]!  @retrieve address found in array SEGS                                @offset by number in R3, put the value                                @ found into R0.
   MOV R3,#0        @we have a match, reset R3 counter to                                @zero.
   LDR R2,=segs     @we have a match, put R2 value back to                                @first element of SEGS
   MOV R3,#0x10     @R3 is COUNTER for matching numbers and                              @the getting value in SEGS                                            @array.
 

Wow, a lot of work.  And I am sure it can be refactored to much simpler code. But for now, it works.

OUTTRO

It became apparent that a lot I take for granted in C has to be done "from scratch" in ASM.  

I had read that programming in ASM can be difficult and time consuming and didn't think a lot about it. But once it in, I had hadn't anticipated how much wheel re-inventing was needed.

This one algorithm a lot of it and its sub-algo took me maybe 30 hours of coding over 3 really long days. 

Ouch! Granted, I am a beginner.....but there it is.

Overall my experience with ARM ASM was both good and bad. 

The positive:I was introduced to a programming pagadigm that improved my knowledge of computing "at a deep level" and had the satisfaction getting a slightly complex algo to work. 

When I put in several test values, hit "step over", and had the correct characters come up on the display (finally!) well, that was a great feeling. 
 
The negative: I also found myself going down my too familiar compulsive rabbit hole. 

Like a gambling addict, my code got close but not quite there, so I tried again, over and over and over. I couldn't tear myself away; I found myself not sleeping, not eating, not showering.  And as always I was more prone to just try things and see what happened then to step away and try to better understand why they happened. 

ARM AS&M became the 20,000 calorie sundae I couldn't stop eating, the butt-terrible violent streaming TV show I had to keep watching, the girlfriend's apartment I couldn't leave. 

So where will I go next?  Maybe I will incorporate fast ASM fragments into larger C projects. Maybe. ASM was a lot of terrible fun, so, maybe/probably. 

But I also have to think about things from the neck down.  Full time ASM could be unhealthy for me.






4 x 1 Mono Mixer--A Confidence Builder

Readers: If you want to build the project featured in today's post, please go to PCBWAY's Community pages--gerber file (main board);...