ocularium v0.1.0 [in-progress]

tiny kite-borne sensor node, collaboration w/ chucho ocampo

2024-08-08 / updated 2024-09-25

Board renders, schematics, interactive bom, and fabrication files on this page were automatically generated by clef + Nix.

interactive bom

ocularium is a tiny sensor platform I designed to be flown on kites. I'm working with Chucho Ocampo on this project, who I met on SEI's trip to Lanzarote.

front
in1
in2
reverse
These board files are actual-size on your screen if your browser is at normal (100%) zoom.

The design is finished, and as of writing this post I'm just sending it out to be fabricated.

Rendered Schematics (PDF)

gerbers.zip

bom

placement file

Warning: these production files have not been validated — I am in the process of converting to kikit (which generates them and puts them here) but spun the boards using Fabrication Toolkit.

design

The board is 30x30mm with a 25x25mm M2 hole pattern. It's powered by a lithium cell via a battery controller, and uses an SD card for data storage. The sensors onboard are a six-axis accelerometer/gyro, a BME680 gas sensor (temperature, humidity, pressure, air quality), a lux sensor, and a MEMS microphone.

Design goals:

software / firmware

I'm writing the firmware in Rust using the embassy family of embedded libraries.

I am currently planning on writing a host-side UI in iced, which I've had luck with in the past (I'm using it for an in-progress mission UI for antrelay).

future work

I will be tying this in with interstice as a networking test platform.

bringup log

2024-08-22

depaneled board, front

Boards came in today. Production went well — they look great. I'm looking at a power issue: the BQ25185 battery charger IC has a regulated output that I was using to supply 3.3V. Problem is that it's actually 4.5V — didn't catch this at design time. In retrospect, I think I had it as a placeholder part that I planned to replace (or augment with a DCDC), but it slipped through the cracks.

Also have an ADC problem — I have VBAT, VUSB, and 3.3V connected to ADC lines, but I didn't add voltage dividers, so they're overvolting the ADCs and I suspect leaking through the ESD protection, if not damaging the silicon. I still have 2 untouched assembled boards if I already managed to burn one out.

Going to have to respin, but provisionally powering 3.3V directly off of my bench supply to enable bringup on this rev and find any more issues.

Tried to connect over SWD with an st-link clone, but it appears they don't support multidrop, which seems to be required for rp2040. Tried flashing black magic probe, which notionally addresses this, but it couldn't see the core. Ordered a few picos (I seem to have run out) to flash picoprobe onto, which I know will work.

2024-08-26

she blinks

Got my picos set up as picoprobes, but no dice. Hooked up to my logic analyzer and saw well-formed SWD traffic. I don't know the protocol so couldn't tell if there was any response data, but I assumed not as probe-rs complained that the device wasn't responding:

$ probe-rs info --chip rp2040 --protocol swd --target-sel 0
Probing target via SWD

Error showing ARM chip information: An ARM specific error occurred.

Caused by:
    0: An ARM specific error occurred.
    1: Failed to connect to the debug port. Please check the debug cable and target power. If SWD multi-drop is used, ensure the correct TARGETSEL value is used.

My priority is get the rp2040 powering on and be able to flash code, so I moved on to triggering BOOTSEL mode. Shorted QSPI_SS to GND and plugged in via USB, but measured voltage on my power supply shot up to > 3.5V and it wasn't sourcing any current, implying that the circuit was powering itself off of USB, even with the switch between VSYS (battery charger output, 4.5V — discussed in last entry) and 3.3V turned off. I don't know how — the only place VUSB goes other than the charger IC are the ADC pins. Could be something fried and bridged the nets, but in this whole process I've neither seen magic smoke nor noticed comonents getting warm — that's no more than speculation at the moment.

On my last untouched board, I cut the VUSB traces right at the USB connector and verified open circuit to the regulator. Again shorted QSPI_SS to ground, powered on with my bench supply, and connected to USB.

Linux tries to connect, but can't talk to the device:

$ dmesg
[1427079.861946] usb 2-1.4.4-port3: attempt power cycle
[1427080.457606] usb 2-1.4.4.3: new low-speed USB device number 91 using ehci-pci
[1427080.869588] usb 2-1.4.4.3: device not accepting address 91, error -32
[1427080.945619] usb 2-1.4.4.3: new low-speed USB device number 92 using ehci-pci
[1427081.357619] usb 2-1.4.4.3: device not accepting address 92, error -32

Seems like progress, but I logic analyzed this too, and the rp2040 isn't even trying to respond — the traffic on the bus is just the host sending SETUP and DATA0 -> GET_DESCRIPTOR packets until it gives up.

There is something happening on the board — when I power it, I see a reproducible pattern of power usage stabilizing at single-digit milliamps on my bench supply, but there's no way for me to tell at present whether that's from the RP2040 or any of the other ICs.

rescue port

Did some digging in the hardware design guide and saw this section about the rescue debug port:

The rescue debug port (DP) on RP2040 can be used to reset the chip into a known state if the user has programmed some bad code into the flash. For example some code that turned off the system clock would stop the processor debug ports being accessed, but the rescue DP would still work because it is clocked from the SWCLK of the SWD interface.

This seems like exactly what I want, because the whole contents of my flash are known to be garbage, as I've never flashed anything to the board before. The question of why I haven't been able to enter BOOTSEL mode is still open, but hopefully this will let me bypass it.

It requires you to use the Raspberry Pi Foundation's own independent fork of openocd (the practice of forking off vendor-specific tooling continues to be a pain in the ass):

$ openocd -f interface/cmsis-dap.cfg -f target/rp2040-rescue.cfg
Info : Using CMSIS-DAPv2 interface with VID:PID=0x2e8a:0x000c, serial=$MY_SERIAL
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : CMSIS-DAP: Interface ready
Now attach a debugger to your RP2040 and load some code

$ openocd -f interface/cmsis-dap.cfg -f target/rp2040.cfg
Info : Using CMSIS-DAPv2 interface with VID:PID=0x2e8a:0x000c, serial=$MY_SERIAL
Info : CMSIS-DAP: Interface Initialised (SWD)
Info : CMSIS-DAP: Interface ready
Info : clock speed 100 kHz
Info : SWD DPIDR 0x0bc12477, DLPIDR 0x00000001
Info : SWD DPIDR 0x0bc12477, DLPIDR 0x10000001
Info : [rp2040.core0] Cortex-M0+ r0p1 processor detected
Info : [rp2040.core0] target has 4 breakpoints, 2 watchpoints
Info : [rp2040.core1] Cortex-M0+ r0p1 processor detected
Info : [rp2040.core1] target has 4 breakpoints, 2 watchpoints
Info : starting gdb server for rp2040.core0 on 3333

(openocd output substantially cleaned for readability).

I connected with gdb, but this failed initially because it was trying to identify my SPI flash and QSPI_SS (= ~BOOTSEL) was still shorted to GND:

Info : accepting 'gdb' connection on tcp/3333
Error: Unknown flash device (ID 0x00ffffff)
Error: auto_probe failed
Error: Connect failed. Consider setting up a gdb-attach event for the target to prepare target for GDB connect, or use 'gdb_memory_map disable'.
Error: attempted 'gdb' connection rejected

Fixed by opening this short:

Info : accepting 'gdb' connection on tcp/3333
Info : Found flash device 'win w25q128fv/jv' (ID 0x001840ef)
Info : RP2040 B0 Flash Probe: 16777216 bytes @0x10000000, in 256 sectors
Info : New GDB Connection: 1, Target rp2040.core0, state: halted

And then flashed blinky firmware:

$ gdb
(ins)(gdb) target extended-remote localhost:3333
Remote debugging using localhost:3333
0x00000030 in ?? ()
(ins)(gdb) file $MY_BLINKY
A program is being debugged already.
(ins)Are you sure you want to change the file? (y or n) y
Reading symbols from $MY_BLINKY...
(ins)(gdb) load
Loading section .boot2, size 0x100 lma 0x10000000
Loading section .vector_table, size 0xc0 lma 0x10000100
Loading section .text, size 0x4cd8 lma 0x100001c0
Loading section .rodata, size 0xaac lma 0x10004ea0
Loading section .data, size 0xb4 lma 0x1000594c
Start address 0x100001c0, load size 23032
Transfer rate: 1 KB/sec, 3838 bytes/write.
(ins)(gdb) run
The program being debugged has been started already.
(ins)Start it from the beginning? (y or n) y

And now probe-rs run (via cargo run) works:

$ cargo run
Finished dev [optimized + debuginfo] target(s) in 0.12s
Running `probe-rs run --chip RP2040 target/thumbv6m-none-eabi/debug/blinky`
    Erasing[00:00:00] [############################] 24.00 KiB/24.00 KiB @ 66.55 KiB/s (eta 0s )
Programming[00:00:00] [############################] 24.00 KiB/24.00 KiB @ 42.71 KiB/s (eta 0s )    Finished in 0.95s
0.001210 INFO  boot

probe-rs info works, but it locks up the core and probe-rs reset doesn't reset it:

$ probe-rs info --chip rp2040 --protocol swd
Probing target via SWD

ARM Chip:
Debug Port: Version 2, MINDP, Designer: Raspberry Pi Trading Ltd, Part: 0x1002, Revision: 0x0

$ probe-rs reset --chip rp2040 --protocol swd
Error: Connecting to the chip was unsuccessful.

Caused by:
    0: An ARM specific error occurred.
    1: An ARM specific error occurred.
    2: Failed to connect to the debug port. Please check the debug cable and target power. If SWD multi-drop is used, ensure the correct TARGETSEL value is used.

This last note about multidrop/TARGETSEL could be the solution, but there isn't a flag to set TARGETSEL for probe-rs reset. Fine in the end — I can manually reset with the power supply.

2024-08-27

Working on USB, seeing the same issue as before: host sees the pull-up and tries to contact the device a few times before giving up:

$ dmesg
[1427079.861946] usb 2-1.4.4-port3: attempt power cycle
[1427080.457606] usb 2-1.4.4.3: new low-speed USB device number 91 using ehci-pci
[1427080.869588] usb 2-1.4.4.3: device not accepting address 91, error -32
[1427080.945619] usb 2-1.4.4.3: new low-speed USB device number 92 using ehci-pci
[1427081.357619] usb 2-1.4.4.3: device not accepting address 92, error -32

I'm testing with firmware that I've flashed to a XIAO RP2040, which successfully establishes a high-speed link and opens a CDC-ACM port which is recognized by the host computer. Expected output:

$ dmesg
[1506678.033057] usb 1-1.5: new full-speed USB device number 22 using ehci-pci
[1506678.113650] usb 1-1.5: New USB device found, idVendor=8888, idProduct=0001, bcdDevice= 0.10
[1506678.113655] usb 1-1.5: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[1506678.113656] usb 1-1.5: Product: $PRODUCT
[1506678.113657] usb 1-1.5: Manufacturer: npry
[1506678.114587] cdc_acm 1-1.5:1.0: ttyACM1: USB ACM device

Probed voltage on D+ and I'm seeing 3.0V with a cable plugged in rather than expected 3.3V for LS operation. D- is at ground potential. Possibly an issue with drive strength on the USB peripheral on the RP2040? But apparently 2.8V is the specified signaling minimum, so perhaps this shouldn't be surprising. Tried a few different USB cables that have been known to work and different ports on the host, no dice.

Probed D+ with USB cable disconnected and I'm seeing 3.20V, which for me counts as effectively nominal for a specified 3.3V.

Tried shorting the 27.4Ω impedance-matching resistors just for something to try — no change.

cc2 / vconn

Found an issue on my schematic: CC2 wasn't terminated — I left it floating. Another thing I intended to get back to at the time, but didn't, and then by the time I went to ship the board, I assumed past-me had validated it.

I'm testing with a USB A-to-C cable, which I suppose must internally have orientation detection logic, so this seems a likely culprit — I definitely need to fix it for the next rev. However, now I'm thinking back to the fact that I got a signal that looked fine on a scope and decoded as valid host-to-device control packets with a logic analyzer, probed right at the impedance-matching resistors next to the rp2040 pins.

I have kept coming back to this idea that maybe my data lines are backwards, but I've checked several times, and they weren't. Then again, I've only been checking by looking at the voltage at D+, the pull-up for which is device-supplied, so possibly that's not a reliable test. And when I scoped/ logic-analyzed the traffic, I was looking at the BOOTSEL behavior, so there could be another confounding issue.

Next thing I guess is to properly terminate CC2 and see if that makes a difference.

2024-09-22

Been almost a month since my last update — haven't gotten around to writing progress up. Mostly been working on molybdos as a basis for the firmware, converting chunks of old projects (e.g. janus, hexrx) to portable library code, and porting third-party sensor libraries to async (bme680, lsm6dsm, veml7700_async).

The problem with USB was swapped polarity on the data lines — I was able to rework it and verify the fix. I thought it was a footprint issue initially, but I actually just swapped them in the schematic at the decoupling resistors.

Also discovered that I got my I2C lines wrong — they're attached to different MCU peripherals. There doesn't seem to be a good software or PIO I2C implementation in Rust yet — I started writing a PIO one based on the example in the RP2040 C SDK, but decided the verification overhead wasn't worth it right at this moment. Going to test by breaking out the bus and connecting with my Bus Pirate or a Xiao board.

2024-09-24

Got a PIO I2S driver semi-working — enough to get working PDM audio back. Scaling the bits to i16::MAX and i16::MIN is enough to get recognizable but choppy sound out of it.

The issue I'm dealing with is that the DMA shots from the PIO FIFOs to memory are taking way longer (40x) than they should. I'm not sure yet whether I'm miscalculating my clock, am missing something about PIO functionality, or if it's something with the host MCU (backpressure or scheduling). I'm running the read loop in the regular thread-mode executor, so it could just be a scheduling issue (though there shouldn't be any other futures running concurrently, so that's kind of dubious). Next thing I'll try is a dedicated higher-priority interrupt executor just because it's easy.

That said, not going to bother for now. I can hear audio, so I know it's working enough to proceed with the next rev.

Confirmed that the BME is reachable over its SPI bus by reading its chip id. Not going to bother trying to read data off of it, just made sure I can talk to it.

2024-09-25

Took a look at the SD card today — it wasn't working right off the bat. Looks like I flipped the MOSI and MISO lines (guessing I feel victim to the SDO/SDI terminology mismatch: CMD is "data in" but "master out" / DAT0 is "data out" but "master in", and I matched on the wrong role). Reworking this would be a pain in the ass because the pins are tiny, so I'm going to just blindly respin it.

Also relevant: last week or so, I broke out my fork of embedded_sdmmc from the hexrx firmare as a standalone repo.

2024-09-26

Had to find my bus pirate yesterday — ran I2C sanity checks today:

bus scan

I2C>(1)
Searching I2C address space. Found devices at:
0x20(0x10 W) 0x21(0x10 R) 0xD6(0x6B W) 0xD7(0x6B R)

Correctly found the VEML7700 at 0x10 and the LSM6DSM at 0x6B.

lsm6dsm

Find WHOAMI reg (0x0f):

I2C>[0xd6 0x0f [0xd7 r]
I2C START BIT
WRITE: 0xD6 ACK
WRITE: 0x0F ACK
I2C START BIT
WRITE: 0xD7 ACK
READ: 0x6A
NACK
I2C STOP BIT

Sees correct value 0x6a.

veml7700

chipid reg at 0x7:

I2C>[0x20 0x7 [0x21 rr]
I2C START BIT
WRITE: 0x20 ACK
WRITE: 0x07 ACK
I2C START BIT
WRITE: 0x21 ACK
READ: 0x81
READ:  ACK 0xC4
NACK
I2C STOP BIT

16 bit registers, default configuration is 0xC4 with address 0x81 — checks out.

respin todo