diff --git a/README.md b/README.md index 235ff4019..0dff12d58 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ App|Description [onboard_temperature](adc/onboard_temperature) | Display the value of the onboard temperature sensor. [microphone_adc](adc/microphone_adc) | Read analog values from a microphone and plot the measured sound amplitude. [dma_capture](adc/dma_capture) | Use the DMA to capture many samples from the ADC. +[read_vsys](adc/read_vsys) | Demonstrates how to read VSYS to get the voltage of the power supply. ### Clocks diff --git a/adc/CMakeLists.txt b/adc/CMakeLists.txt index 65399b695..49dccf8cc 100644 --- a/adc/CMakeLists.txt +++ b/adc/CMakeLists.txt @@ -5,4 +5,5 @@ if (NOT PICO_NO_HARDWARE) add_subdirectory(joystick_display) add_subdirectory(onboard_temperature) add_subdirectory(microphone_adc) + add_subdirectory(read_vsys) endif () diff --git a/adc/read_vsys/CMakeLists.txt b/adc/read_vsys/CMakeLists.txt new file mode 100644 index 000000000..67535bd72 --- /dev/null +++ b/adc/read_vsys/CMakeLists.txt @@ -0,0 +1,30 @@ +add_library(power_status_adc INTERFACE) +target_sources(power_status_adc INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/power_status.c + ) +target_include_directories(power_status_adc INTERFACE + ${CMAKE_CURRENT_LIST_DIR} + ) +target_link_libraries(power_status_adc INTERFACE + hardware_adc + hardware_gpio + ) + +add_executable(read_vsys + read_vsys.c + ) +target_include_directories(read_vsys PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ) +target_link_libraries(read_vsys + pico_stdlib + power_status_adc + ) +if (PICO_CYW43_SUPPORTED) + target_link_libraries(read_vsys + pico_cyw43_arch_none + ) +endif() + +pico_add_extra_outputs(read_vsys) +example_auto_set_url(read_vsys) diff --git a/adc/read_vsys/power_status.c b/adc/read_vsys/power_status.c new file mode 100644 index 000000000..226835f9e --- /dev/null +++ b/adc/read_vsys/power_status.c @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "stdbool.h" +#include "hardware/adc.h" +#include "power_status.h" + +#if CYW43_USES_VSYS_PIN +#include "pico/cyw43_arch.h" +#endif + +#ifndef PICO_POWER_SAMPLE_COUNT +#define PICO_POWER_SAMPLE_COUNT 3 +#endif + +// Pin used for ADC 0 +#define PICO_FIRST_ADC_PIN 26 + +int power_source(bool *battery_powered) { +#if defined CYW43_WL_GPIO_VBUS_PIN + *battery_powered = !cyw43_arch_gpio_get(CYW43_WL_GPIO_VBUS_PIN); + return PICO_OK; +#elif defined PICO_VBUS_GPIO_PIN + gpio_set_function(PICO_VBUS_GPIO_PIN, GPIO_FUNC_SIO); + *battery_powered = !gpio_get(PICO_VBUS_GPIO_PIN); + return PICO_OK; +#else + return PICO_ERROR_NO_DATA; +#endif +} + +int power_voltage(float *voltage_result) { +#ifndef PICO_VSYS_PIN + return PICO_ERROR_NO_DATA; +#endif +#if CYW43_USES_VSYS_PIN + cyw43_thread_enter(); + // Make sure cyw43 is awake + cyw43_arch_gpio_get(CYW43_WL_GPIO_VBUS_PIN); +#endif + + // setup adc + adc_gpio_init(PICO_VSYS_PIN); + adc_select_input(PICO_VSYS_PIN - PICO_FIRST_ADC_PIN); + + adc_fifo_setup(true, false, 0, false, false); + adc_run(true); + +#if CYW43_USES_VSYS_PIN + // We seem to read low values from cyw43 sometimes - this seems to fix it + int ignore_count = PICO_POWER_SAMPLE_COUNT; + while (!adc_fifo_is_empty() || ignore_count-- > 0) { + (void)adc_fifo_get_blocking(); + } +#endif + + // read vsys + uint32_t vsys = 0; + for(int i = 0; i < PICO_POWER_SAMPLE_COUNT; i++) { + uint16_t val = adc_fifo_get_blocking(); + vsys += val; + } + + adc_run(false); + adc_fifo_drain(); + + vsys /= PICO_POWER_SAMPLE_COUNT; +#if CYW43_USES_VSYS_PIN + cyw43_thread_exit(); +#endif + // Generate voltage + const float conversion_factor = 3.3f / (1 << 12); + *voltage_result = vsys * 3 * conversion_factor; + return PICO_OK; +} \ No newline at end of file diff --git a/adc/read_vsys/power_status.h b/adc/read_vsys/power_status.h new file mode 100644 index 000000000..2df75d05d --- /dev/null +++ b/adc/read_vsys/power_status.h @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef POWER_STATUS_H +#define POWER_STATUS_H + +/*! + * \brief Get power source + * + * Returns whether battery powered + * \note On Pico W must have called cyw43_arch_init + * + * \param battery_powered True if powered by battery, False if powered by USB or another means + * \return Zero if the battery status can be determined, an error code otherwise \see pico_error_codes + */ + +int power_source(bool *battery_powered); + +/*! + * \brief Get system voltage + * + * Returns the system voltage + * \note Must have called adc_init + * + * \param voltage Calculated voltage + * \return Zero if the voltage can be determined, an error code otherwise \see pico_error_codes + */ +int power_voltage(float *voltage); + +#endif \ No newline at end of file diff --git a/adc/read_vsys/read_vsys.c b/adc/read_vsys/read_vsys.c new file mode 100644 index 000000000..bb9864c08 --- /dev/null +++ b/adc/read_vsys/read_vsys.c @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include +#include +#include "hardware/adc.h" +#include "pico/float.h" + +#if CYW43_USES_VSYS_PIN +#include "pico/cyw43_arch.h" +#endif + +int main() { + stdio_init_all(); + + adc_init(); + adc_set_temp_sensor_enabled(true); + + // Pico W uses a CYW43 pin to get VBUS so we need to initialise it + #if CYW43_USES_VSYS_PIN + if (cyw43_arch_init()) { + printf("failed to initialise\n"); + return 1; + } + #endif + + bool old_battery_status; + float old_voltage; + bool battery_status = true; + char *power_str = "UNKNOWN"; + + while(true) { + // Get battery status + if (power_source(&battery_status) == PICO_OK) { + power_str = battery_status ? "BATTERY" : "POWERED"; + } + + // Get voltage + float voltage = 0; + int voltage_return = power_voltage(&voltage); + voltage = floorf(voltage * 100) / 100; + + // Display power if it's changed + if (old_battery_status != battery_status || old_voltage != voltage) { + char percent_buf[10] = {0}; + if (battery_status && voltage_return == PICO_OK) { + const float min_battery_volts = 3.0f; + const float max_battery_volts = 4.2f; + int percent_val = ((voltage - min_battery_volts) / (max_battery_volts - min_battery_volts)) * 100; + snprintf(percent_buf, sizeof(percent_buf), " (%d%%)", percent_val); + } + + // Also get the temperature + adc_select_input(4); + const float conversionFactor = 3.3f / (1 << 12); + float adc = (float)adc_read() * conversionFactor; + float tempC = 27.0f - (adc - 0.706f) / 0.001721f; + + // Display power and remember old vales + printf("Power %s, %.2fV%s, temp %.1f DegC\n", power_str, voltage, percent_buf, tempC); + old_battery_status = battery_status; + old_voltage = voltage; + } + sleep_ms(1000); + } + +#if CYW43_USES_VSYS_PIN + cyw43_arch_deinit(); +#endif + return 0; +}