Jim's Depository

this code is not yet written
 

There is an internal temperature sensor hiding in the RP2040 processor. It is just ever so slightly tricky to read. I notice that a lot of the help on the web skips an important step where you enable the temperature sensor, and glosses over the ADC reference voltage.

It isn't a high precision sensor, about ½ degree Celsius is as good as you are going to read, but it will let you know if your enclosure is cooking your project.

Enable the temperature sensor

In addition to adc_init() you will need to adc_set_temp_sensor_enabled(true). I kind of think there is a period of time before you would not trust the reading. It seems like it creeps up for a second or two. So if you are using this, just turn it on and leave it on.

Understand the ADC reference voltage

The ADC system is all relative to a reference voltage. If you haven't connected anything to the ADC_VREF pin of a Pi Pico, then it is nominally 3.3v, and that constant is present in everyone's sample code. It is worth noting that it is probably lower than that, so if you want to calibrate, check the pin with a multimeter.

If you are interested in accuracy with other analog inputs, you will probably put a 'real' voltage reference on ADC_VREF, then you need to change the constants in the code to match your chosen hardware.

My Example

In any event, my example looks like this.

/*                                                                                                                                    
** NOTE THEE WELL:                                                                                                                    
**                                                                                                                                    
** You must do these before using this function...                                                                                    
**                                                                                                                                    
**  adc_init();                                                                                                                       
**  adc_set_temp_sensor_enabled(true);                                                                                                
**                                                                                                                                    
** You will get ridiculous temperature values if you don't enable                                                                     
** the temperature sensor. It also might take a little bit to converge                                                                
** on something close to right, so don't be tricky and snap it on and                                                                 
** off to save power.                                                                                                                 
**                                                                                                                                    
*/

#define REFERENCE_VOLTAGE 3.3

void print_info(void) {
    adc_select_input(4);
    uint16_t bits = adc_read();
    float voltage = bits * REFERENCE_VOLTAGE / 4095;
    float temperature = 27.0 - ( voltage - 0.706) / 0.001721;

    printf("Internal temperature: %4.1fC\n", temperature);
}

I put this little Makefile in the top of my Pico project directory. It handles making the build directory and invoking cmake with the right flags to build my actual make files.

Mostly I just do make install to build and flash onto my device. (Presuming the device is in UF2 mode, that's a different post.)

#
# Path to my SDK installation
#
PICO_SDK_PATH ?= ~/pico-sdk

#
# Path to the mount point for my pico
#
PICO_MOUNT ?= /rpi-rp2

#
# Path to my UF2 image in the build directory
#
IMAGE = build/sbi-weather.uf2

all : $(IMAGE)

install : all
	mount $(PICO_MOUNT)
	cp $(IMAGE) $(PICO_MOUNT)/foo
	sync

clean :
	rm -rf build pico_sdk_import.cmake

build :
	mkdir build

$(IMAGE): build/Makefile
	( cd build ; make all )

build/Makefile : CMakeLists.txt pico_sdk_import.cmake | build
	( cd build ; cmake -DPICO_SDK_PATH=$(PICO_SDK_PATH) .. )

pico_sdk_import.cmake : $(PICO_SDK_PATH)/external/pico_sdk_import.cmake
	cp $< $@

.PHONY : all install clean $(IMAGE)

Note

I'm using a Raspberry Pi running Raspbian as my host. In the install target the sync command is helpful to push the dirty blocks out right now.

The mount command also works fine for my user account because I have this in my /etc/fstab

LABEL=RPI-RP2 /rpi-rp2 msdos defaults,noauto,user 0 1

NOTE: THIS IS ALL OBSOLETE, DO NOT USE!

Find the reset_usb_boot() function in pico/bootrom.h instead.

But for historical reasons, and maybe you want to render it unbootable…

I want to be able to erase and reprogram my Pi Pico without having to touch it and only having a USB cable to it.

I built my program to use USB for the console. If the console receives a "reset" command then it erases the secondary boot loader from flash and forces a restart. This will make it boot into the UF2 loader.

The function looks like this…

#include "wipe.h"
#include "hardware/watchdog.h"
#include "hardware/flash.h"
#include "hardware/sync.h"
#include "pico/multicore.h"

// Remember: Put hardware_flash and pico_multicore in the CMakeLists.txt target_link_libraries

void wipe(void) {
    const uint8_t blank[256] = {0};

    // Stop core1. We better be core0.                                                                                                
    multicore_reset_core1();

    uint32_t saved = save_and_disable_interrupts();
    // Wipe the secondary boot loader                                                                                                 
    flash_range_program( 0, blank, 256);
    restore_interrupts(saved);

    // this will reset us in 1 millisecond.                                                                                           
    watchdog_enable( 1, false);

    // await our doom                                                                                                                 
    while( true);
}

No more physical access required while developing. Until I do something stupid to hang the CPU.

Subtle Note:

I am using flash_range_program instead of flash_range_erase because I want to do only 256 bytes. Otherwise I clobber the code I'm executing and everything ends in tears. In the world of this flash chip, "erase" means "set to all ones" and "program" means "set the zeros to zero". So I can turn the 256 bytes into all zeros without erasing.

Danger:

If you decide you want to erase the whole flash, there is going to be trouble. You will end up erasing the code you are executing and hang. I would suggest you move the watchdog_enable call above your flash_range_erase (which you will use instead of program so you don't need a buffer) and give it a timeout long enough to accomplish the erase.

Ostensibly you can mark your function as being copied to RAM, but I didn't have success with that. If I used the CMakeLists.txt to make the whole program be in RAM I could erase flash willy nilly without problems.

I'm going to try the Raspberry Pi Pico for some of my small projects. I'm primarily attracted by the deterministic timing available with the Programmable I/O state machines.

I'll mostly be building devices which present as USB peripherals to a host. As such I probably won't bother with the UART for debugging messages during development. I'd also like to avoid the Serial Wire Debug (SWD) so I don't have to wire that up either.

Developer Ergonomics

That presents me with a little development conundrum. I don't like doing the unplug/press/plug/release dance, and the human quadrature dance with a reset button added isn't much better.

My current plan is to build a "destroy yourself" function into my USB interface. That will erase the secondary boot loader block in flash then do a reset from software to force a USB Mass Storage mode boot.

Mounting on Debian

I'm using a Raspberry Pi 4 as a development machine, we'll see. It takes it about 3 seconds to build my 15kB test program. That's really pretty sad. It is mostly cmake generated abominations faffing about. Hopefully it doesn't get much longer as I add real code.

I want a target in my Makefile to kill a running device, flash new, and restart it. That means mounting the mass storage device when it becomes available. For now I've got a line in /etc/fstab to make that easy...

LABEL=RPI-RP2 /rpi-rp2 msdos defaults,noauto,user 0 1

With that my untrusted user account can mount /rpi-rp2 and do what it needs.

If you are thinking things changed, you are probably getting the dark mode support. It's your browser's choice. I send the same stuff to you, but the CSS is now littered with @media (prefers-color-scheme: dark) { sections to change things up for dark mode.

I wish Safari didn't put all the editable text, radio buttons, and buttons in blinding white. I wonder if I gorked something up there.

Update: Yeah. It was me. Somewhere in the process of switching to use CSS variables for my colors the problem went away. I probably had a background forced to something unfortunate.

I've switched from the Ink markdown processor to Down. I liked that Ink was pure swift, but I kept spending time enhancing it to meet my needs. Down uses libcmark, which I'm not wild about having inside my server, but hopefully I won't spend any more time thinking about it.

It's a testament to both that swapping took just a couple minutes. Half of it finding the typo in my Packages.swift file.

print("Hello World! I'm beautiful!")

As long as I was fiddling with femtoblogger I touched up the CSS so code will stand out a little better, and then splurged and added Prism to highlight source code for you. I feel a little bad about the 12k of javascript, but you only load it once. We will survive this.

So far I recommend Prism. It was a CSS add in <HEAD> and a <SCRIPT> add by </BODY> and everything worked.

The biggest add to femtoblogger is an out of band management interface, but you don't get to see that.

I'm a big fan of Swift Argument Parser. It generally makes command line argument processing painless.

But…

That assumes you are in its primary use case as the main program's command processor.

What if you want to use it to drive a CLI?

You no longer have a primary command, you'll have to sort that out from the first argument. You could do that outside with a dictionary, or make a single fake command and make the rest subcommands. This has some frustrating shortcomings. It would be nicer to add a layer for "one of many commands".

What if your command needs context?

When my SSH command executer in my server wants to run a command, it needs to know how to write its output back to the remote SSH client. The .run() methods of ArgumentParser don't take arguments. The commands themselves are structs and really don't want you to be clever and make them classes.

What if the shell didn't break my command into an array of string?

I weep each time I see someone break a command with .split(separator:" "). I mean, at least remember to set the omittingEmptySubsequences that you might not know exists. But what about quoted strings? What about backslashes? What if I want one of the arguments to be an empty string?

Problem Solved

I built ParsableCommands which adds a "one of many commands" layer to ArgumentParser and includes a proper string tokenizer if you need it. Easy to use and very little code.

The context issue is a little harder. The are an astonishingly large number of ways to fail at this in Swift. I tried things for about 6 hours before settling on a per-context-type derived protocol of ParsableCommand, adding an new .run(context:MyType) method to ParsableCommand, and using a switch to look at the command between parsing and running and figure out which kind of context it needs from its type.

This doesn't lend itself to a package solution, but the example program in the ParsableCommands package shows how to do it.

Synergy

Combining this with the SSHConsole works very well for having an out of band diagnostic and management interface to an HTTP server.

I have a number of servers written in Swift. Mostly these are HTTP backends. As anyone who has written backends knows, you always want to query some information out of them or maybe change a setting. Then ensues a carving off if the URL space, writing a bunch of command handlers, fretting over how to keep users out of them, and a creeping need to fuss with the HTML.

Earlier this week I got nerd sniped by the Swift NIO group when they released SwiftNIO SSH and problem solved!

I built SSHConsole to trivially add an SSH listening port to your existing server and let you build your management commands as plain text generating commands.

Thoughts on NIOSSH

  • There was a fair bit of frustration getting public key authentication working, but mostly because it was too easy. Some more documentation would help here, but you can just look at my example Echo program and the library and see how it all works.
  • The private key datatype is overly opaque. It makes you rewrite part of it so you can serialize and retrieve your SSH host key.

Thoughts on the Rest of the Project

  • I actually ended up wasted a lot more time struggling with Swift Argument Parser since I was outside its normal use case. There's another post on that.
  • As usual, dressing up for GitHub and adding the inline documentation took about as long as the design and coding. And I left out the high level documents because I was tired at the end. You'll have to live with the examples.

I notices NIOSSH today for SSH support in Swift. I'm going to try it as a management console for one of my HTTP backends. For an idea of how much effort is involved I'll give a running tally here.

T+0

  • Go look at https://github.com/apple/swift-nio-ssh. Read a lot of it.

  • Discover that there is already an example server hiding in Sources/NIOSSHServer

  • Copy it in, drop out the code for port forwarding

T+1hr

  • I can connect to my server, and it crashes. Progress.

  • Realize the sample server is trying to use /usr/local/bin/bash which I don't have.

  • Get rid of all the pipe code since I'm not going to be interactive, at least yet. Introduce a bunch of bugs. Get lost in event loops, futures, and promises and the mysteries of unwrapInboundIn and friends.`

T+2hr

  • Success! I have built a really complicated echo server.

T+4hr

  • Much better understanding of NIO ChannelHandler, threw out about half the previous code.

  • Got in a fight with Swift over how to say a parameter conforms to a protocol. Ended up with an ugly generic solution. Not happy.

  • Boiled down to a SSHCommandHandler class which is used as a channel handler. It gets a command and dispatches it to a doCommand(command:to:environment:) function which you override.

  • The Protocol fight was because I just want that to: parameter to be a TextOutputStream. I don't want to tell you about my specific version. That collides with the inout nature of the print(_:to:inout TextOutputStream) call.

T+6hr

Mostly liking the shape of the code. In your main or somewhere likely you will…

let console = SSHConsole(passwordDelegate: Authenticator())
try console.listen()

…later you can…

try? console.stop().wait()`

T+10hr

And just like that I hit a wall. Swift Argument Parser does not have a mechanism for you to pass in state as you dispatch the command. There is a run() with no arguments and that's it. I spent 4 hours exploring the vast array of things which don't work. I landed on a few that do, but they are a bit gross.

Here's what I ended up with:

  • Define a struct for my context.

  • Extend the ParsableCommand to require a context variable of my type. I called it ConsoleCommand

  • I define my commands with structs conforming to ConsoleCommand. Each one has to have a var context:ConsoleContext? = nil in it.

  • Now my run() can look at its context and everything is happy

  • Except… ParsableCommand conforms to Decodable and an Optional<Foo> only conforms if Foo conforms, and my context object can't conform because there is no sane null value for an output stream!

  • The textbook solution is to specify my own Codable keys and leave out the trouble maker, except these objects are full of variables because they have entries for all the command syntax elements.

  • … hours are wasted …

  • I finally implemented a @transient property wrapper for nil-able values which sets them to nil on a decode and doesn't write on an encode. This is inspired by a feature rejected by the Swift designers.

  • I don't like it. I resent having to put a @transient var context:ConsoleContext?=nil in every command, but when I tried using classes instead of structs for the commands it didn't parse, so that may not be supported, so no inheriting my boilerplate.

  • I may go back and make my context be Decodable even if I have to make some insane values to populate it. Then I can lose the property wrapper and also save some unwrapping.

All in all a typical day of programming. Lot's of good progress then I step in a bear trap and have to chew my leg off before continuing.

T+20 Hours

All wrapped up and published on GitHub with API level documentation and very little high level documentation. Just the way I hate to use software. You can find it at SSHConsole

I was typing up my thoughts on using SwiftUI and broke femtoblogger! My screed gets truncated about half way through.

I'm not sure if that means I should:

  • settle down?
  • fix femtoblogger?
  • tighten up my writing?

Who am I kidding… where did I leave that source code…

Update: I try to be a good modern programmer and haul in functionality from strangers on GitHub instead of spending weeks writing it myself… and then… BAM. The Markdown to HTML translator I'm using would allow raw HTML through, which in a public environment is irresponsible, so I kill any HTML I find. Sadly, if you include <table> in your comment, the markdown engine thinks everything from the opening tag to the end is the HTML and I kill it.

I guess I'll have to add a real "safe mode" to the markdown translator. The author hasn't integrated a pull request in months, that is kind of deterrent to making nice additions.

Update: Three hours to study the markdown processor enough to figure out how to cleanly add a safe mode and tests. I'd have been done faster until I realized it was written by John Sundell. Google frequently brings me his articles for solving Swift conundrums. He deserves an extra tidy patch.

more articles