This Time Using Raspberry Pi Pico
Who hasn’t heard of the Raspberry Pi? As a follow-up to my previous article and a personal challenge, in this article I build another radar speed monitor, this time using the $4 Raspberry Pi Pico.
I have to try something I’ve never done before. I’m going to replicate last month’s project (“Just How Fast Was I Driving, Officer?,” Circuit Cellar #389, December, 2022) [1], using a whole different microcontroller (MCU) and radar module—one that I haven’t used before, the Raspberry Pi Pico (Figure 1). There was a lot of press about it a while ago because it was being sold for $4. It was difficult to get your hands on one for quite some time. The supply has loosened up, but I haven’t seen a lot of projects using it.
The Raspberry Pi Pico is an MCU board based on the Raspberry Pi RP2040 MCU chip (Figure 2). It was designed to be a low-cost, yet flexible development platform for RP2040. Table 1 lists the key features and peripherals.
The Pico provides minimal external circuitry to support the RP2040 chip, comprising flash (Winbond W25Q16JV), crystal, power supplies and decoupling, and a USB connector. The majority of the RP2040 MCU pins are brought to the user IO pins on the left and right edges of the board. Note that four of the RP2040 IOs are used for internal functions: driving an LED, on-board Switched Mode Power Supply (SMPS) power control, and sensing the system voltages.
SPRECHEN SIE DEUTSCH?
Unlike other PI boards, the Pico board doesn’t run a full operating system. You must embed your own application, written in either MicroPython or C. Since the release, there is also an installation for the Arduino IDE. I won’t be using any of these—instead I’ll be writing the app using an interpreter, MMBasic, which is a Microsoft BASIC-compatible implementation of the BASIC language, with floating point, integer and string variables, arrays, long variable names, and many other features.
When you plug in the Pico while holding down the BOOTSEL button, a virtual drive is created called “RPI-RP2” (the same as if you had plugged in a USB memory stick). Drop the PicoMiteV5.07.04.uf2 file onto the drive, and the Pico will restart and automatically create a virtual serial port. The LED on the Raspberry Pi Pico will blink slowly, indicating that the PicoMite firmware with MMBasic is now running.
— ADVERTISMENT—
—Advertise Here—
The emphasis with MMBasic is on ease of use and development. The development cycle is very fast, with the ability to switch instantly from edit to run. Errors are listed in plain English, and when an error occurs a single keystroke will invoke the built-in editor, with the cursor positioned on the line that caused the error.
A note about the on-board editor: You also need a terminal emulator program running on your desktop computer. The terminal emulator should support VT100 emulation, since that is what the editor built into the PicoMite expects. There is more on this in the PicoMite user’s manual [2].
For Windows users, it is recommended that you use Tera Term, which has a good VT100 emulator and is known to work with the XModem protocol—which you can use to transfer programs to and from the PicoMite. I’m most comfortable with a mouse-style editor, so I will not use this internal editor. Instead I will use MMEdit as an alternate tool, suggested by PicoMite authors Peter Mather, Geoff Graham, and Mick Ames [2]. MMEdit is a Windows editor that will allow you to edit, store, and retrieve your file, then automatically download your basic text file to Pico, save and run it. Seven programs, 1-7, can be saved to the Pico, and you can have any one of these autostart on power up. They can also be chained or called by others without losing variable contents.
Ctrl-C exits a running program to a command prompt. In Command mode “>” you can enter direct commands such as memory, and get some real-time feedback about the resources available (Figure 3). Arduino users will feel comfortable, because many commands will be familiar. Although the syntax is a bit different, the user manual will explain all!

I’m using MMEdit to write my MMBasic application. Upon clicking the action menu item and selecting deploy, the Maximite Control Centre pops up. Here an application is loaded into the Pico, saved and executed. If an application is running, you can exit with a Ctrl-C. This “>” prompt is the command mode. I’ve entered the memory command, which displays the real time memory stats.
Let’s get into the first program, which will allow us to investigate a new radar module, the K-LD2 Radar Transceiver from RFbeam Microwave GmbH (St. Gallen, Switzerland). This unit is smaller than the Grove BGT24LTR11 I used in last month’s project [1]. While the K-LD2 uses a UART communication interface, the format is a bit different. It has eight command classes that separate registers into groups (Table 2). The communication format is shown in Table 3.
A typical Read Command request/response might be:
$S06<CR>
$S0601<CR/LF>
A Write Command request/response might be:
$S0602<CR>
$S0602<CR/LF>
APPLICATION 1: INVESTIGATE K-LD2
Of the two applications written for this month’s column, the first is by far more complicated, because I want to allow users to play around with all the registers of the K-LD2, so they feel comfortable adjusting any default parameter that may not be the best selection for their application. The complications arise because a user needs to choose any register, and make adjustments and report the result using words, rather than just a value.
As shown in Listing 1, we begin the application with some variable initialization. A second serial port is set up for the K-LD2. Finally, subroutine showMainMenu displays the main menu and looks for some user input.
— ADVERTISMENT—
—Advertise Here—
VT100 commands allow some basic screen formatting. After each user input, the screen is cleared and a menu is displayed. The main menu presents all the classes (Listing 2). The subroutine mainMenuChoice gets user inputs, and if it matches one of the classes, a different menu is displayed. All menus work in a similar way.
Listing 1
The first application begins with some initialization which includes variables and a second serial port. The main loop (do while 1) contains a single routine in which all else is based, showMainMenu.
‘target port\COM4:115200 s\picomite
Dim STRING KLDCommand$, KLDResponse$
Dim STRING SignOn$ = “PI PICO Radar”
Dim FLOAT version!
Dim INTEGER C03Array%(1023), verticalPosition = 1, horizontalPosition = 1, myPause = 100, myDebug = &B00000001
‘
SetPin GP21, GP20, COM2 ‘ assign TX and RX for COM2 (K-LD2 radar PCB from Rfbeam.ch)
Open “COM2:38400,4096” As #2
Print SignOn$
hitEnterToContinue ‘ routine to wait for user input
‘
Do While 1
showMainMenu ‘ routine to display main menu and look for user input
Loop
End
Listing 2
The showMainMenu subroutine displays choices to the user, and then calls the mainMenuChoose routine to get user entry and determine what to do next. The choice will direct the program flow to display a sub menu.
Sub showMainMenu
clearScreen
homeCursor
horizontalPosition = 10
positionCursor
Print “Main Menu Class”
Print “A - Array Parameters”
Print “C - Complex Read Parameters”
Print “D - Detection Parameters”
Print “F - FLASH Read Parameters”
Print “R - Real Time Read Parameters”
Print “S - System Parameters”
Print “T - Testing Parameters”
Print “W - Basic Write Parameters”
Print
Print “Enter a Class Choice”
mainMenuChoose
end sub
sub mainMenuChoose
userEntry
Select Case myString$
Case “A”
showMenuA
Case “C”
showMenuC
Case “D”
showMenuD
Case “F”
showMenuF
Case “R”
showMenuR
Case “S”
showMenuS
Case “T”
showMenuT
Case “W”
showMenuW
End Select
End Sub
Listing 3
In the second application we have data placed in LEDArray() via subroutine processValue. We need to transmit this data to the DotStar LED strip via SPI() command. Each of the two display digits are made up of seven segments with four LEDS per segment. Since each LED requires four bytes, LEDArray() holds 2 x 7 x 4 x 4 or 224 bytes.
SUB SendLEDFrame
processValue
IF myDebug AND 1 THEN
PRINT “Shifting Data Out”
ENDIF
SendStartFrame
FOR i = 0 TO (NumberOfPixels * 4)-1 STEP 4
received_data = SPI(LEDArray(i))
received_data = SPI(LEDArray(i+1))
received_data = SPI(LEDArray(i+2))
received_data = SPI(LEDArray(i+3))
IF myDebug and 1 THEN
IF LEDArray(i+1) = 0 AND LEDArray(i+2) = 0 AND LEDArray(i+3) = 0 THEN
print “0”;
ELSE
print “1”;
ENDIF
ENDIF
NEXT
IF myDebug AND 1 THEN
PRINT
ENDIF
SendEndFrame
END SUB
Listing 4
The processValue subroutine loads the LEDArray() with the proper data depending on the digits and color needed. Digit segment data is held in segDefArray. Each array byte is the data for a digit 0-9, and each bit indicates whether that segment of the digit should be illuminated or not.
SUB processValue
FOR i=Digits-1 TO 0 STEP -1
digitArray(i) = myValue\10 ‘integer disvision
segmentArray(i) = segDefArray(digitArray(i))
IF myDebug AND 16 THEN
print “myValue is “;myValue
print “digitArray(“;i;”) is “;digitArray(i)
print “segmentArray(“;i;”) is “;segmentArray(i)
ENDIF
myValue = (myValue - (digitArray(i) * 10)) * 10
NEXT
i=0
FOR D=0 TO Digits-1
FOR S = Segments TO 1 STEP -1
FOR L = LEDsPerSegment-1 TO 0 STEP -1
‘DO = D * Segments * LEDsPerSegment
‘SO = (S-1) * LEDsPerSegment
‘LO = L * LEDsPerSegment
if(segmentArray(D) AND 1<<S) THEN
LEDArray(i) = &HE0 + Brightness
LEDArray(i+1) = BlueColor
LEDArray(i+2) = GreenColor
LEDArray(i+3) = RedColor
ELSE
LEDArray(i) = &HE0
LEDArray(i+1) = 0
LEDArray(i+2) = 0
LEDArray(i+3) = 0
END IF
i=i+4
NEXT
NEXT
NEXT
END SUB
Listing 5
The input pin GP19 indicates when a legal detection has taken place and sets the flag bit. If mySpeedMPH is not = SpeedLimit (in my case 20), then make mySpeed and myValue = SpeedLimit and turn off all the LEDs. This keeps the display blank most of the time. The display is updated by one of the two timer interrupts—one to continuously flash the measured speed if flag = 1, and the second to flash the speed limit every 10 seconds if flag = 0.
DO WHILE 1
IF PIN(GP19) = 1 THEN
flag = 1
ELSE
flag = 0
ENDIF
IF mySpeedMPH <> SpeedLimit THEN
mySpeedMPH = SpeedLimit
myValue = mySpeedMPH
getblank
sendLEDFrame
ENDIF
LOOP
END
If you choose “C,” you would then see the “Class C Menu” and choose “4.” You would see the results shown in Figure 4.

The Maximite Control Centre is a pop-up VT100 terminal that is called up by executing a deploy action from the MMEditor. The application from the text editor would be automatically downloaded, saved and run. Here I’ve halted the program with Ctrl-C. At the command prompt “>” I’ve entered the memory command to see real-time stats.
Note that if the register can be written to, you will get this prompt; just hit ENTER to continue without changing anything. You can investigate all of this further if you like. It’s worth mentioning here the ability to change two parameters, Hold time and Sensitivity, in real time, via two potentiometers connected to two input pins. These inputs are then read, and the analog conversion value is used to choose one of 10 values available for the actual register parameter. While this is the default, you can disable these pots (inputs) and change these register parameters values directly through the interface (that is, this application).
Hold time is the number of seconds to hold a detection. It can be one of ten values—0.2 to 160 seconds. The Hold time register is a pointer to one of those 10 values held in registers A00:A09.
Sensitivity is a value of radar signal in decibels (dB) at which it is considered detected. It can be one of 10 values—0dB to 34dB. The Sensitivity register is a pointer to one of those 10 values held in registers A10:A19.
Note that registers A20:A27 hold eight potential filters. A value of 0 disables a filter, and other values 1-127 are identified by one of the filter “bins,” which are determined by the sampling rate. Register D08 determines the bin width for all filters. This allows the elimination of false triggers from an object within the radar field of view that may be causing detections, such as a rotating fan. This is an advanced function, and you will most likely want to know more about the relation between sampling rate, bins, and speed. (See the K-LD2 datasheet [3] available on the RFbeam website.)
Figure 5 is a block diagram of the K-LD2 circuitry. Since it is similar in function to the Grove radar that I described in Circuit Cellar last month [1], you might also refer to that column for more background information.

This is a block diagram of the K-LD2 radar module. Note the similar RF front end with internal I/Q inputs to the signal processing unit.
APPLICATION 2: RADAR AND DOTSTAR LEDS
With an understanding of the K-LD2 radar unit, we can get on with the digit-display application. Note that no libraries are used for either the radar module or the DotStar LED interface. The DotStar requires an SPI interface. Adafruit’s DotStar is a continuous string of individually-addressable RGB LEDs; it is an input-only device. While each device has an output, this is a shifted output meant to daisy chain additional DotStar devices. As explained in my column last month [1], I’ve taken a DotStar strip of 60 devices and cut it up into four device segments. These segments become the seven parts of a seven-segment digit, shown in Figure 6. Each digit is wired to the next to create two large, seven-segment digits.

I made tiny square PCBs to interconnect the segments at right angles, which make an S-shaped path through the seven-segment digit. Digits can be cascaded. Make sure you arrange the in and out ends of the segments correctly. If you flip them all around from this orientation you can connect from the left. Just make sure that your data is arranged correctly, based on your choice of direction flow!
The SPI output requires the updating of 56 LEDs (4 LEDs/segment x 7 segments x 2 digits = 56 LEDs). Each LED requires a 4-byte data field consisting of 1 brightness byte, plus 1 byte for each of the three colors red, green, and blue. The total packet requires 4 bytes (start field), followed by 56 x 4 bytes (data field), and 4 bytes (stop field) for a total of 232 bytes. I have separate routines for sending the start and stop data fields. The variable received_data is just a dummy variable, since nothing is connected to the SDI input. As you can see, the the SPI() function is simple—it sends the byte (in parentheses) out the SDO, receives a byte from SDI and places it in received_data.
it in received_data.
SUB SendEndFrame
received_data = SPI(&HFF)
received_data = SPI(&HFF)
received_data = SPI(&HFF)
received_data = SPI(&HFF)
END SUB
SUB SendStartFrame
received_data = SPI(&H00)
received_data = SPI(&H00)
received_data = SPI(&H00)
received_data = SPI(&H00)
END SUB
The routine SendLEDFrame handles the whole process. The LEDArray of LED data is just shifted out the SPI port. Note that my debugging print statements in Listing 3 show the actual bit stream being sent. These can be disabled by changing myDebug = 0.
But the true work is handled in the processValue routine at the beginning of the SendLEDFrame routine. Because I’m shifting bits into the right side of the display, the first data in will go through digit ‘0’ to the last segment in the LED chain of digit 1. DigitArray() will hold the tens digit and the units digit of the variable myValue. I’ve predefined the 10 possible 7-bit shifts that will produce the digits 0-9 in segDefArray(). Note that Bit0 is not used.
Dim INTEGER segDefArray(9) = (&B11101110, &B10001000, &B01111100, &B11011100, &B10011010, &B11010110, &B11110010, &b10001100, &B11111110, &b10011110)
As shown in Listing 4, the value in digitArray() indexes the data in segDefArray() to populate the segmentArray(). Finally, for each LED in a segment LEDsPerSegment 3:0, for each segment in a digit Segments 7:1, and for each digit in the chain Digits 1:0, we determine what is actually stored in the LEDArray() for each LED based on the Segments bit data in segmentArray(). If the bit is ‘1’ the segment is lit up (with some color). If the bit is ‘0’ the segment is unlit (blank or black).
All of this takes place any time some speed (in miles per hour) needs to be updated. And that, my friends, comes from the K-LD2.
After we do the required initialization of the serial port, the SPI port, and any registers in the K-LD2 radar module that we need to change from the default values, we come to the last command that forms an endless loop, DO WHILE 1… LOOP (Listing 5). Here, the flag reflects the state of the GP19 pin, the detectOut of the radar module. When a detection is valid, this output will go high for the holdTime from register S0C. Then, if mySpeedMPH is not the constant SpeedLimit (20mph), it is set to SpeedLimit. This prevents the rest of this routine from executing more than once.
The rest of the routine blanks the display when mySpeedMPH is not the constant SpeedLimit. If it seems as though something is missing here, you’re right. I have two timer interrupts set, and they handle the digits and colors displayed.
— ADVERTISMENT—
—Advertise Here—
SETTICK 10000, showSpeedLimit, 1
SETTICK 200, showSpeed, 2
The first executes the routine showSpeedLimit every 10 seconds. We simply return, unless flag = 0 (no detection), then set myValue to SpeedLimit. The routine getSpeedLimit sets the digit color to WHITE, and the routine sendLEDFrame shifts out the myValue digits. Finally, mySpeedMPH is set to zero to trigger the blanking of the display. It is ON for just a quick flash. When there is no detection, the posted speed limit is flashed every 10 seconds.
The second routine, showSpeed, is executed every 200ms. We simply return, unless flag = 1 (detection); otherwise we send Command $C00 to the radar module. This gives back three items—the detection register, the detection bin, and the detection strength. If bit 1, the direction of detection register, is “toward” the radar, then the speed is calculated from the myBin number and mySamplingSpeed using the equation:
And so:
mySpeedKMH = (myBin * mySamplingRate * 1280) / (256 * 44.7)
mySpeedMPH = mySpeedKPH * 1.60934
Before we send this speed to the LEDs, we check to see if it’s greater than the SpeedLimit, and make the color RED; otherwise we make the color GREEN. myValue = mySpeedMPM and the routine sendLEDFrame shifts out the myValue digits. Finally mySpeedMPM is set to zero to trigger the blanking of the display. It is ON for just a quick flash. Flashing the display allows for a lower average current draw. While there is a detection active, the detected speed is flashed multiple times per second.
CONCLUSION
I hope I’ve been able to demonstrate that you can take many paths to accomplish a task. While you may choose a path based on the best tool for the job, often there are many possible options, all of which will get you over the finish line. Don’t be afraid to jump right in and try something new. The learning curve might require a bit more effort, but the rewards are considerable. Adding to your repertoire will make you a more valuable resource.
Figure 7 shows the completed project, ready to mount inside the enclosure described previously [1].
![Figure 7
This project is ready to mount inside the enclosure I described in my column last month [1]. The Gel-cell serves as the 6.0-7.2V supply. The solar panel has a built-in charger. A buck/boost switching regulator gives the DotStar LEDs a hard 5V, whereas the Pi Pico and K-LD2 run on 3.3V.](https://i0.wp.com/circuitcellar.com/wp-content/uploads/2023/07/390-Bachiochi-figure_7.jpg?resize=379%2C506&ssl=1)
This project is ready to mount inside the enclosure I described in my column last month [1]. The Gel-cell serves as the 6.0-7.2V supply. The solar panel has a built-in charger. A buck/boost switching regulator gives the DotStar LEDs a hard 5V, whereas the Pi Pico and K-LD2 run on 3.3V.
My fond memories of programming the TRS-80 Model 1 in the late 1970s began with the supplied BASIC interpreter. It is good to see that this old standby is still around, by making its presence known to a new MCU and a new crowd that may not even know of BASIC’s existence. Congratulations to the Raspberry Pi crew on the release of the RP2040, their flagship MCU chip.
Recently the Raspberry Pi Pico W was released, adding Wi-Fi to the Pi Pico 40-pin DIP format. A Cypress CYW43439 chip enables a 2.4GHz WLAN IEEE 802.11 b/g/n MAC/baseband/radio, and Bluetooth 5.0 compliance. For $6, I’m sure it will be a big hit as well. That peripheral uses an SPI communication channel, so you should still be able to use MMBasic with the device. Presently MMBasic has direct support for these peripherals:
- SD Cards with FAT16 or FAT32 file systems up to 32GB
- LCD, OLED and e-Ink display panels from 0.96” to 3.5” (diagonal), with resolutions up to 480×320 pixels
- Touch Sensitive LCD panels
- Real-Time Clocks using the PCF8563, DS1307, DS3231 or DS3232 chips
- Infrared Remote Control support allowing a Sony or NEC IR remote control
- Temperature and humidity measurement using the DS18B20 or DHT22/DH11 sensors
- Distance measurement using the HC-SR04
- Numeric keypads with a 4×3 or a 4×4 keypad layout
- WS2812 multi-color LED chips
All these features are built into the BASIC interpreter, so there is no need to load libraries or write special code. It’s possible we may see Wi-Fi support added in the future, but until then, don’t be afraid to write your own support routines. For Arduino usage you might take a look at the Earle Philhower Arduino core, which is used to to put Arduino on all your favorite RP2040 devices [4]. It has lots of peripherals supported, including Wi-Fi. So many options, so little time.
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • JANUARY 2023 #390 – Get a PDF of the issue
Sponsor this ArticleJeff Bachiochi (pronounced BAH-key-AH-key) has been writing for Circuit Cellar since 1988. His background includes product design and manufacturing. You can reach him at: jeff.bachiochi@imaginethatnow.com or at: www.imaginethatnow.com.