pimoroni / enviro

MIT License
101 stars 79 forks source link

Reinstatement of voltage monitor #145

Open Julia7676 opened 1 year ago

Julia7676 commented 1 year ago

I am attracted to the range of Enviro boards by there ability to operate for months unattended using AA cells. in addition to reliability (see issue #119) battery monitoring is essential for my application. I have been considering a HW workaround consisting of a pair of external resistor feeding VSYS /3 to ADC(2). A far better solution would be to get the PICO W voltage monitor working on ADC(3) as intended.

This is notoriously difficult and may require changes to the micropython firmware outside of the scope of the enviro software. The difficulties arise because the RP2040 pin used for the voltage monitor is shared with the WiFi chip.

The following code from 0.0.8 show the code that needed to be removed to improve stability.

# all the other imports, so many shiny modules
import machine, sys, os, json
from machine import RTC, ADC
import phew
from pcf85063a import PCF85063A
import enviro.helpers as helpers

# all the other imports, so many shiny modules
import machine, sys, os, json
from machine import RTC, ADC
import phew
from pcf85063a import PCF85063A
import enviro.helpers as helpers

# read battery voltage - we have to toggle the wifi chip select
# pin to take the reading - this is probably not ideal but doesn't
# seem to cause issues. there is no obvious way to shut down the
# wifi for a while properly to do this (wlan.disonnect() and
# wlan.active(False) both seem to mess things up big style..)
old_state = Pin(WIFI_CS_PIN).value()
Pin(WIFI_CS_PIN, Pin.OUT, value=True)
sample_count = 50
battery_voltage = 0
for i in range(0, sample_count):
  battery_voltage += (ADC(29).read_u16() * 3.3 / 65535) * 3
battery_voltage /= sample_count
battery_voltage = round(battery_voltage, 3)
Pin(WIFI_CS_PIN).value(old_state)

# set up the button, external trigger, and rtc alarm pins
rtc_alarm_pin = Pin(RTC_ALARM_PIN, Pin.IN, Pin.PULL_DOWN)
external_trigger_pin = Pin(EXTERNAL_INTERRUPT_PIN, Pin.IN, Pin.PULL_DOWN)

There is nothing wrong with the code that reads the voltage except that 50 reads is a bit excessive. The problem is how it interacts with WiFi.

1) The code should not touch the WIFI_CS_PIN. If WIFI_CS_PIN is ever False during a ADC read it is a indication that WiFi is active. We must ensure that WiFI is inactive (easier said than done).

2) The digital drivers connected to GPIO29 need to be completely disabled. (see dection 2.19.4. Pads of the RP2040 datasheet).

3) Extreme measures needed to be implement to endure that WiFi is off. It appears to be active whilst using Thonny even before import network.

The following code is showing promise but given the nature of past issues needs further testing.

WIFI_CLK_ADC_PIN              = 29  # add to constants 

import network
wlan=network.WLAN()
wlan.disconnect()
wlan.active(False)
wlan.deinit()

def setPadCtr(gpio, value):
    mem32[0x4001c000 | (4 + (4 * gpio))] = value

def getPadCtr(gpio):
    return mem32[0x4001c000 | (4 + (4 * gpio))]

padCtrSave = getPadCtr(WIFI_CLK_ADC_PIN)
setPadCtr(WIFI_CLK_ADC_PIN, 0x80)

sample_count = 4
battery_voltage = 0
for i in range(0, sample_count):
    battery_voltage += (ADC(WIFI_CLK_ADC_PIN).read_u16() * 3.3 / 65535) * 3
battery_voltage /= sample_count
battery_voltage = round(battery_voltage, 3)

setPadCtr(WIFI_CLK_ADC_PIN,padCtrSave)
ZodiusInfuser commented 1 year ago

Good luck with this! Not to put a downer on it already, but that second set of code looks similar to some that I tried months back from either reddit or the pi forum, which sadly still caused issues.

I hope you can get somewhere with it though, as I feel like it will benefit a lot of people if you do. The Micropython system solution I talked about in the other thread, though it would work, is only really applicable to Enviro (and our other RTC equipped Pico W Aboard products), and only gives you that single reading at bootup.

Gadgetoid commented 1 year ago

Related - https://github.com/raspberrypi/pico-examples/pull/331

I'm not above rolling a C module for us to accomplish this, while we wait for an official solution.

Julia7676 commented 1 year ago

It has been a while since I visited this topic.

IMPORTANT: I found that after switching the battery voltage to the ADC pin it is necessary to allow time for the voltage to settle. I used a delay of 1 ms and have not investigated whether a shorter delay would suffice.

The following code has been successfully working on my enviro for some time:-


def setPadCtr(gpio, value):
    mem32[0x4001c000 | (4 + (4 * gpio))] = value

def getPadCtr(gpio):
    return mem32[0x4001c000 | (4 + (4 * gpio))]

padCtrSave = getPadCtr(WIFI_CLK_ADC_PIN)
setPadCtr(WIFI_CLK_ADC_PIN, 0x80)
time.sleep_us(1000)  ## alow voltage to settle after reconnect
sample_count = 4
battery_voltage = 0
for i in range(0, sample_count):
    battery_voltage += (ADC(WIFI_CLK_ADC_PIN).read_u16() * 3.3 / 65535) * 3
battery_voltage /= sample_count
battery_voltage = round(battery_voltage, 3)
setPadCtr(WIFI_CLK_ADC_PIN,padCtrSave)

I now understand that whilst PYTHON runs there are other higher priority tasks that can unexpectedly interrupt the python script. Since there is no way from PYTHON to 'disable interrupts' great care is needed to ensure that WIFI and anything this else that may affect the WIFI_CLK_ADC_PIN pin is dormant. Presumably a C module would be able to prevent interruption by other processes however I beleive my suggested code adequately prevent interuption by the wifi.

I mention the following since not working with Thonny was one of the original reasons for removal. I have found, in general, that activity from Thonny can cause unexpected hiccups in my python scripts. To avoid this when debugging using Thonny I always execute my application by using a soft reset.

from machine import reset reset()