b-parasite-esp32/resources.md
2021-03-15 22:40:09 +01:00

33 KiB

My development board

Articles

  • Great article about using Rust and Apache Mynewt, but also covers J-Link, ST-Link, openocd, unlocking the nrf52. Link on medium

nrf command line utilities

  • nrfjprog Used for programming hex files using the J-Link programmer.
  • nrfutil Used for DFU - device firmware update - via the USB port. No J-Link. This is what Adafruit uses for uploading code via USB. This is "higher level" and requires a pre-existing bootloader in the nrf52 chip that understands DFU. The Adafruit_nRF52_Bootloader is such a bootloader, which we can burn using nrfjprog + J-Link once. Adafruit has its own version, adafruit-nrfutil.

SWD vs. Bootloader vs. DFU

  • How nrfutil is used in the platformio nordicnrf52 package: link
  • Seems like we can use DFU if we install a DFU-enabled bootloader like the Adafruit_nRF52_Bootloader
  • Using the adafruit nrf52 bootloader with the E73-TBB link

Burning the Adafruit bootloader

  1. $ brew cask install nordic-nrf-command-line-tools -> Didn't work. I had to manually install the pkgs from nordic
  2. pip install intelhex
  3. PATH=$PATH:/Users/rbaron/.platformio/packages/framework-arduinoadafruitnrf52/tools/adafruit-nrfutil/macos make BOARD=feather_nrf52840_express all

Burning our custom bootloader

The error I was making was using a non-softdevice bootloader. The following works:

# Compile
$ PATH=$PATH:/Users/rbaron/.platformio/packages/framework-arduinoadafruitnrf52/tools/adafruit-nrfutil/macos make BOARD=e73_tbb all

# Flash (make sure you use the _s132_ hex file)
$ JAVA_HOME=/Users/rbaron/homebrew/Cellar/openjdk/15.0.1 nrfjprog --program _build/build-e73_tbb/e73_tbb_bootloader-0.4.0-2-g4ba802d_s132_6.1.1.hex --sectoranduicrerase -f nrf52 --reset

Now we can drop the JLink and use the USB serial for uploading/monitoring.

Uploading with nrfutil

$ ~/.platformio/packages/framework-arduinoadafruitnrf52/tools/adafruit-nrfutil/macos/adafruit-nrfutil dfu genpkg --dev-type 0x0052 --sd-req 0x00B7 --application .pio/build/e73-tbb/firmware.hex dfu-pkg.zip
Zip created at dfu-pkg.zip

$ ~/.platformio/packages/framework-arduinoadafruitnrf52/tools/adafruit-nrfutil/macos/adafruit-nrfutil dfu serial --package dfu-pkg.zip -p /dev/cu.usbserial-14330 -b 115200
Upgrading target on /dev/cu.usbserial-14330 with DFU package /Users/rbaron/dev/parasite/code/parasite/dfu-pkg.zip. Flow control is disabled, Dual bank, Touch disabled
########################################
########################################
########################################
########################################
########################################
#######################
Activating new firmware
Device programmed.

Linking

In platformio-core/tools/pioplatform.py:

if "build.ldscript" in board_config:
    env.Replace(LDSCRIPT_PATH=board_config.get("build.ldscript"))

In platformio-core/platformio.py:

# append into the beginning a main LD script
if env.get("LDSCRIPT_PATH") and not any("-Wl,-T" in f for f in env["LINKFLAGS"]):
    env.Prepend(LINKFLAGS=["-T", env.subst("$LDSCRIPT_PATH")])

The -T flag expects a path to a linker script, as per ld docs.

Frameworks, such as the nordicnrf52, contain boards definitions that specify the ldscript.

  1. For the adafruit_feather_nrf52832 board, we have:
"arduino":{
  "ldscript": "nrf52832_s132_v6.ld"
},

Which is defined here.

  1. For the nrf52_dk, we have:
"arduino":{
  "ldscript": "nrf52_xxaa.ld"
},

Which is the most common linker script, and is part of the nordic official SDK, it seems, available here. For reference, check out the linker for the Adafruit nRF52 Bootloader. Here you can see the memory map of the nRF52 Feather.

Same platform, different framework

Both the Feather nRF52832 and the Generic boards above point to the same platform, the nordicnrf52. Inside this platorm, there's a switch for selecting which framework to use, based on the board name, here:

if self.board_config(board).get("build.bsp.name",
                                            "nrf5") == "adafruit":
                self.frameworks["arduino"][
                    "package"] = "framework-arduinoadafruitnrf52"

What does each line in the linker mean?

Memory layout

The SoftDevice S132 spec specifies the layout as the soft device begins at addr 0x0 and the application code comes after it, at the address APP_CODE_BASE.

PWM

Analog to digital converter (ADC)

  • In nrf52-land, the ADC is called SSADC
  • Example in the sdk: in examples/peripheral/saadc/main.c
  • Docs from Adafruit. It uses Arduino's analogRead.

Resoultion

The default resolution is 10 bits (1024 values),

Reference value

The default reference is an internally supplied 3.6V. It seems like we woulld only be able to read values up to 3.6V. This might be enough if we're using a CR2032 coin cell, but might not if we use a LiPo (4.2V fully charged).

Question: what happens if Vcc < 3.6?

There is a possibility of using Vcc as a reference. Then I imagine 0 -> 0V; 1024 -> Vcc. This might be exactly what we want: In a PWM positive pulse, our RC_parasitic will rise to 0.63Vcc in R*C_para time. If we use Vcc as a reference for the ADC, I think we "cancel out" the Vcc factor.

  • How adafruit uses the Vcc reference: link
  • How arduino-nRF5 uses the Vcc refenrece: link

So in Arduino land, using AR_VDD4 does all we need: use nrf's SAADC_CH_CONFIG_REFSEL_VDD1_4 (Vcc/4 ref) and a gain of 4 (SAADC_CH_CONFIG_GAIN_Gain1_4). While connected via USB, with:

analogReference(AR_VDD4);
int sens_val = analogRead(kSensAnalogPin);

I'm getting ~680 when in the air; ~65 while holding the sensor. The default resolution is 10 bits, so 1024 -> 3.3V. On the scope, I'm reading the sensor output value to be 2.16V. This matches the value I'm reading: 1024/3.3 * 2.15 = 667. Nice.

The Vcc voltage seems to have an impact though (with nrf52840): Vcc = 3.5 => Moisture Air: 668; Hand: 65 Vcc = 3.0 => Moisture Air: 640; Hand: 53 Vcc = 2.5 => Moisture Air: 605; Hand: 32 Vcc = 2.0 => Moisture Air: 547; Hand: 23

Possible issues:

  • Fast discharging circuit is not fully open with lower voltage?
  • Weird capacitance stored in the transitor messes up the quick switching?
  • Time it takes to overcome the 0.6V diode drop in the output circuit?

Possible fixes:

  • Measure and correct it via a software interpolation

For now I'm implementing the software fix that seems to work well in practice. The measurements and regression coefficients are in this spreadsheet.

Battery

  • CR2032 datasheet
    • 235 mAh (from 3.0V to 2.0V)
    • Typical discharge current: 0.19 mA -> 1200 hours

Battery monitoring

  • Good post on how to measure lipo batteries: link We have a choice of using different references when using the analog-to-digital converter. For measuing the peak of the RC charging circuit, it makes sense to use VCC as the reference, as the rise in the RC is proportional to VCC. For battery monitoring, we need an absolute reference. Luckily, we can use the internal reference of 0.6V. To increase the range of values, we can combine this with a gain parameter. This is what the arduino-nrf5 does in wiring_analog_nRF52.c. With a gain of 1/2, we could read the absolute range of [0, 1.2V]. Since we're interested in the max value of roughly 4.2 (fully charged LiPo, or alternatively 3.0V for a CR2032 coin cell), we can use a voltage divider with R1 = 1470 kOhm and R2 = 470kOhm. This would give us a range of [0, ~5V]. This seems to be working okay, but I need to investigate if making it stiffer (lower R1 and R2) improves the accuracy. With higher resistor values, we minimize the quiescent current, but increase the source impedance. Even hooking up the oscilloscope changes the reading value.

There is a way to measure Vcc without any external circuitry. It uses the NRF_SAADC_INPUT_VDD. See this post on devzone.

Ideas for improvement:

  • Decrease the impedance of the voltage divider, but somehow use a mcu-controlled switch so we don't pay the current price when the MCU is sleeping (which is most of the time). This stackexchange answer mentions a similar approach
  • Use even larger resistor values and attach a capacitor across R2, as suggested by this answer. We can reach nanoamps of current, which is negligible in this design. Question: does the capacitor self leak? If so, we'd need to be constantly pumping charges into it. How significant is this effect? If we do oversampling, the capacitor probably won't charge in time for multiple fast measurements. But the capacitor might act like a filter itself, negating the need for oversampling.

BLE

  • BLE examples for platformio with nordicnrf52 platform: link
  • nrf5 BLE examples are way more complicated than I need for now
  • How the alternative firmware for the xiaomi temp sensor works: link
    • The MCU is a Telink TLSR8251
    • Deep sleep current is 1-2uA
    • It sends an advertisement packet every 1 minute with the MAC, temperature, humidity and battery level
    • main
    • main_loop
    • advertisement_data definition
    • set_adv_data - where the temp, humidity and batt levels are encoded into the advertisement data
    • They don't use manufacturer data. They use a custom UUID service (0x181a).
    • Using the manufacturer data requires a SIG membership for getting the manufacturer ID (first two bytes in the manufacturer data)
    • Using the service data also requires applying for a 16-bit (2 byte) ID from SIG (link)
    • Aha! I think we can use the 0x181a service UUID (the same one the alternative xiaomi firmware uses). This service is reserved for "Environmental Sensors". Check out the offical assigned numbers list here.
      • We can use the SERVICE_DATA (int 16) in the BLE advertisement packet to pack service data for the UUID 0x181a.
  • How the xiaomi BLE temp sensor works in ESPHome: link
  • How advertisement works YouTube video by nordic
    • In your case:
      • GAP role: broadcaster
      • Advertisement type: legacy ADV_NONCONN_IND (non-connectable broadcast)
      • Advertising data starts at 22:17
        • The payload might contain 37 bytes in the classic/legacy protocol:
          • addr (6 bytes) (either public or random)
            • Public has privacy concerns
            • Random can be static or private (resolvable or non-resolvable)
          • data (31 bytes)
      • The 31 bytes of advertisement data can be split into structures
        • Each structure has a length (1 byte), a type (n bytes) and data (remaining bytes)
        • Examples of structures are service UUID, local name, manufacturer specific data
        • Manufacturer specific data is usually the way to transmit data in the advertisement packet
          • Requires a company id. Check out the list. Adafruit has the 0x0822.
  • BLEPeripheral seems to be a popular library choice
    • setManufacturerData is probably what we want
    • It seems to be highly geared towards nrf51 SOCs, although it mentions support for some nrf52 ones
  • Adafruit_nRF52_Arduino
  • Can we update the manufaturer advertising data dynamically?
  • What MAC address to use?
    • Public - might have some security implications, since BLE can be used for tracking
    • Random - safer, since its random, but it "breaks" our MAC-addr based Hub config

Packing values into the advertising packet

Soil humidity

If there's room, I'd like to pack the raw analog-to-digital value and a percentage.

  • Values for the ADC are 10 bit (0-1023). So we could use 10 bits for this. For simplicity, I'll keep this byte-aligned and use 16 bits (2 bytes) for this. It means I'll have to scale [0, 2^10) to [0-2^16) (and back in the hub).
  • Percentage values are floating points, but we don't have nearly as much precision for justifying 32/64 bits. Even using integers in [0-100] would be fine. I'll splurge and use two bytes here as well. It means I'll have to scale [0, 1.0] to [0, 2^16) and back in the hub.

Central BLE (hub)

The "central" BLE will be responsible for listening to parasite's BLE broadcasts and parsing its manufacturer's data. One idea is to use ESPHome for this, for example like the xiaomi sensor does:

Question: parasite will advertise a lot of packets in short bursts. How is this data "deduplicated"? I imagine ESPHome won't parse all the packets and forward them to MQTT.

OTA

Measuring current consumption

  • Good issue on the xiaomi sensor tracker

Data persistence

Questions:

  • What addresses are safe to write to? We need to avoid:
    • Bootloader
    • SoftDevice
    • Application
    • More sections?

Deep sleep

  • Adafruit_nRF52_Arduino has a waitForEvent function, which internally calls sd_app_evt_wait.

  • System ON/OFF. System ON mode is a power saving mode in which the RTCounters are active, so they can emit events for waking up the CPU. System off has no RTC active.

  • the RTC COMPARE event can be used to wake up. See the examples/peripheral/rtc.

  • app_timer on nordicsemi

  • Adafruit_nRF52_Arduino issue

  • Loopless / low power examples pull request on Adafruit_nRF52_Arduino

    • They use the SoftwareTimer, which
  • The example in examples/peripheral/rtc is straight-forward: initialize RTC and set up the TICK and COMPARE0 event. The TICK event is triggered on every clock cycle. The COMPARE0 only when the counter reaches a specified value.

    • It uses the nrf_drv_rtc.h header, which doesn't seem to be included in the Arduino core implementaton
    • There's a similar one in Arduino called nrfx/hal/nrf_rtc.h. It seems to use the same call to nrf_rtc_cc_set under the hood.
    • nrfx is "a standalone set of drivers for peripherals present in Nordic Semiconductor's SoCs".
  • The RTC driver has some interesting functions:

    • nrfx_rtc_cc_set
    • nrfx_rtc_tick_disable
  • Interrupt handlers are registered in the NVIC (nested vectored interrupt controller)

*wolfssl uses the functions I'm thinking of using.

  • Reminders:

    • RTC0 and RTC1 might be in use. Try using RTC2
    • Initialize the low frequency clock LFCLKSRC
    • Disable the TICK interrupt
    • Set up an IRQ (callback) example from nordic forum
    • Set up the COMPARE[0] event/interrupt
    • Disable LFCLKSTARTED interrupt? link from nordic forum
    • FPU (floating point interrupts) also might trigger the interrupt. Important thread from nordic
    • Doesn't work with C++?? devzone
  • Good tips on how to save energy (for nrf51, but the same ideas apply) link

Deep Sleep with SoftwareTimer

This is a FreeRTOS scheduled task. It seems to work, but by default it wasn't. I had to increase the FreeRTOS timer stack size, as hinted in this GitHub issue], in ~/.platformio/packages/framework-arduinoadafruitnrf52/cores/nRF5/freertos/config/FreeRTOSConfig.h.

  • Issues
    • nrfx_clock_init is not linked by default in platformio. Is there a more conventional way of using this?
    • freeRTOS on Adafruit_nRF52_Arduino uses the RTC. link

Arduino specifics

FreeRTOS

Nordic specifics

  • HAL vs. Drivers
    • Great answer on nordic forum
    • HAL are lower-level. They provide some functions for accessing registers. Drivers are higher level, and usually use the HAL functions. Drivers can be used with or without SoftDevices.

nRF52840

I've started using a standalone nrf52840 module (e73-2g4m08s1c), since I want a barebones module to test power consumption. This chip is very similar to the nrf52832, except it has built-in usb support!

Adafruit bootloader

The bootloader is different. It has one extra feature: double resetting puts the chip into DFU/CDC mode. I actually verified that it shows up as a mass storage device on my mac!

Questions

  • Booting in DFU/CDC creates a new serial device in /dev/cu.usbmodem*. What about when the application is running? How does Serial.print() work? How can I read those?
    • framework-arduinoadafruitnrf52/cores/nRF5/TinyUSB/Adafruit_TinyUSB_ArduinoCore/Adafruit_USBD_CDC.h likely has the answer and it uses TinyUSB.
  • Reset button seems to be "configurable" to use pin 0.18. Do I need to pre-configure it or will resetting work with Adafruit's bootloader for putting it into flash mode by quickly resetting it twice?

Power input

  • When connected to USB, VBUS is 5V
  • The datasheet mentions 1.7V - 5.5V supply voltage
  • But there are two supply pins (page 79):
    • VDD handles 1.7 - 3.6V
    • VDDH handles 2.5 - 5.5V
  • So, when connecting to USB, do I need to bridge VBUS and VDDH high?
    • (page 65): "As a consequence, VBUS and either VDDH or VDD supplies are required for USB peripheral operation"

Bluetooth

I got basic sketches working on the barebones e73c breakout, except the ones running bluetooth. I was able to deviry that the program hands exactly when it reaches the Bluefruit.begin() function. Googling suggests that it's missing a low frequency oscillating crystal.

Details on oscillator on page 84.

How to enable the internal synthetic one?

In arduino-nRF5 there is a menu in which we can select:

  • Crystal (-DUSE_LFXO)
  • RC (-DUSE_LFRC)
  • Synthetic (-DUSE_LFSYNT)

Changing the following on framework-arduinoadafruitnrf52/variants/feather_nrf52840_express/variant.h made it work:

// #define USE_LFXO      // Board uses 32khz crystal for LF
#define USE_LFRC    // Board uses RC for LF

Using the raw nrf5 SDK

  • SparkFun article on how to setup vscode for development link

Dev boards

Timers, RTC

  • devzone thread:
    • Timers use the HFCLK; better resolution, more power usage
    • RTC uses the low frequency source LFCLK; less accurate, less power usage I want RTC, since I don't need to be super precise and I want to save as much battery as possible.

Power down

  • powerdown examples for nrf51 and nrf52
  • How to measure current with the dev kit using an oscilloscope. Docs by nordic
  • Good thread on devzone about current measurement link
  • Another thread on how to minimize current consumption on devzone

There is something very weird going on. The current usage of my module varies wildly with the input voltage (Vcc). Something is off:

  • Vcc = 2.5 => 15uA
  • Vcc = 3.3V => 1ma
  • Vcc = 3.6 => > 2ma

Very weird. What I did:

  • Tried the same sketch with a barebones nrf52832 module (E73-2G4M04S1B). It seems to behave as expected: around 2uA current in the input range 2V-3.6V;
  • Soldered another nrf52840 to another breakout board. Now I'm seeing ~4uA in the input range 2V-3.6V;

This suggestes there was something wrong with my previous nrf52840 module. Maybe I abused it too much or shorted something while soldering (but I wasn't able to find it with a contuniuity test). Also the system on might be because the nrf52840 has more RAM to keep powered on. We can selectively disable some, I think.

Anyway, I'm considering this solved.

A side note: the E73-2G4M04S1B module uses the "old" nrf52832 module, but it shows lower current and it has a 32kHz crystal built in, which the E73-..C does not. I think the old module might be a better choice for us. The only downside is that it's slightly larger, but shouldn't matter at all.

So the latest data is:

  • System ON sleep: ~4uA
  • System OFF sleep: 0.3uA (pretty cool!)

BLE

I had to change the LFCLK source in these params: #define NRFX_CLOCK_CONFIG_LF_SRC 0 #define NRF_SDH_CLOCK_LF_SRC 0 #define CLOCK_CONFIG_LF_CAL_ENABLED 1 #define NRF_SDH_CLOCK_LF_ACCURACY 1

Debugging

  • Basics of debugging with SEGGER embedded studio video

Error handling

  • Nice post on devzone link

RC vs. Crystal

  • Good post with pros/cros on devzone Botom line:
  • RC is "cheaper" (doesn't require crystal of course)
  • RC is omre power hungry
    • Needs to be recalibrated often (requires turning on the HFCLK of 16MHz)
    • Accuracy is worse, so requires bigger windows for transmitting
  • All in all it should increase of 8-10uA on average
  • github.com/joric/nrfmicro/wiki/Crystal

Question: can we pay this cost only when the BLE is transmitting? Answer: I don't think so, since the RTC depends on the LFCLK. In theory we could let go of the calibration during system on sleep and let it drift by however many seconds it does. It won't matter if it drifts for a dozen seconds on a 5 minutes sleep. But it starts to get tricky to implement and measure. The best solution is to just stick a crystal on the board.

DEC capacitors

The nrf52832 SoC requires a few decoupling caps on its DEC* pins. User NeverDie took some good pics of the module with the cap removed and it seems like the capacitors are already there: link

Electronics

Temperature sensor

Comparison table form Sensirion: link

  • SHT31 is the one I bought for testing
  • SHTC3
    • ~ $1.5 each
    • Google JLC support for assembly link
    • 0.3uA in sleep
    • KiCad footprint on GitHub

Antenna