~aleteoryx/muditaos

f0503057f715b3239d611600bee01097db6f3753 — wojtekrzepecki 4 years ago 3596802
[MOS-109] Add current measurement scope

added current measurement transfer over logs to plot
it on PC side
M cmake/modules/ProjectConfig.cmake => cmake/modules/ProjectConfig.cmake +3 -0
@@ 24,6 24,9 @@ else()
    set (LOG_REDIRECT "RTT_JLINK" CACHE INTERNAL "")
endif()

# add CurrentMeasurement enable option
option(CURRENT_MEASUREMENT "CURRENT_MEASUREMENT" OFF)

# add USB-CDC echo test enable option
option(USBCDC_ECHO "USBCDC_ECHO" OFF)
if (${USBCDC_ECHO} STREQUAL "ON")

M module-bsp/board/rt1051/puretx/CMakeLists.txt => module-bsp/board/rt1051/puretx/CMakeLists.txt +12 -0
@@ 11,6 11,7 @@ target_sources(

    PRIVATE
        hal/battery_charger/BatteryCharger.cpp
        hal/battery_charger/CurrentMeasurementScope.cpp
        hal/key_input/KeyInput.cpp
        bsp/battery_charger/battery_charger.cpp
        bsp/eink/eink_pin_config.cpp


@@ 30,3 31,14 @@ target_sources(
        board/irq_gpio.hpp
        board/BoardDefinitions.hpp
)

if((${PROJECT_TARGET} STREQUAL "TARGET_RT1051") AND (${CURRENT_MEASUREMENT} STREQUAL "ON"))
    set (ENABLE_CURRENT_MEASUREMENT_SCOPE 1 CACHE INTERNAL "")
else()
    set (ENABLE_CURRENT_MEASUREMENT_SCOPE 0 CACHE INTERNAL "")
endif()

target_compile_definitions(module-bsp
    PRIVATE
        ENABLE_CURRENT_MEASUREMENT_SCOPE=${ENABLE_CURRENT_MEASUREMENT_SCOPE}
)

M module-bsp/board/rt1051/puretx/bsp/battery_charger/battery_charger.cpp => module-bsp/board/rt1051/puretx/bsp/battery_charger/battery_charger.cpp +9 -41
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "battery_charger.hpp"


@@ 55,7 55,7 @@ namespace bsp::battery_charger
            /// Initial parameters for fuel gauge battery model

            constexpr std::uint16_t LearnCFG    = 0x2602;
            constexpr std::uint16_t FilterCFG   = 0xCEA4;
            constexpr std::uint16_t FilterCFG   = 0xCEA0; // Current filter Tc = 0.7s
            constexpr std::uint16_t MiscCFG     = 0x01D0;
            constexpr std::uint16_t RelaxCFG    = 0x2039;
            constexpr std::uint16_t RCOMP0      = 0x0070;


@@ 476,33 476,7 @@ namespace bsp::battery_charger
            std::uint8_t temperatureInt = value.second >> 8;
            return utils::twosComplimentToInt(temperatureInt);
        }
        /*
        int getCurrentMeasurement()
        {
            auto value = fuelGaugeRead(Registers::Current_REG);
            int current;
            // 2's compliment into decimal
            if (value.second & 0x8000) {
                // negative numbers
                std::bitset<16> currentBitset = std::bitset<16>(value.second - 1);
                currentBitset.flip();
                current =
                    static_cast<int>((static_cast<std::uint16_t>(currentBitset.to_ulong()) * -1) * currentSenseGain);
            }
            else {
                // positive numbers
                current = static_cast<int>(value.second * currentSenseGain);
            }
            return current;
        }

        int getCellVoltage()
        {
            auto value  = fuelGaugeRead(Registers::VCELL_REG);
            int voltage = value.second * voltageSenseGain;
            return voltage;
        }
        */
        void chargingFinishedAction()
        {
            LOG_DEBUG("Charging finished.");


@@ 750,19 724,13 @@ namespace bsp::battery_charger
    int getAvgCurrent()
    {
        const auto [_, value] = fuelGaugeRead(Registers::AvgCurrent_REG);
        int current;
        // 2's compliment into decimal
        if (value & 0x8000) {
            // negative numbers
            std::bitset<16> currentBitset = std::bitset<16>(value - 1);
            currentBitset.flip();
            current = static_cast<int>((static_cast<std::uint16_t>(currentBitset.to_ulong()) * -1) * currentSenseGain);
        }
        else {
            // positive numbers
            current = static_cast<int>(value * currentSenseGain);
        }
        return current;
        return static_cast<int>(utils::twosComplimentToInt(value) * currentSenseGain);
    }

    int getCurrentMeasurement()
    {
        auto [_, value] = fuelGaugeRead(Registers::Current_REG);
        return static_cast<int>(utils::twosComplimentToInt(value) * currentSenseGain);
    }

    MaxMinVolt getMaxMinVolt()

M module-bsp/board/rt1051/puretx/bsp/battery_charger/battery_charger.hpp => module-bsp/board/rt1051/puretx/bsp/battery_charger/battery_charger.hpp +3 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 94,6 94,8 @@ namespace bsp::battery_charger

    int getAvgCurrent();

    int getCurrentMeasurement();

    MaxMinVolt getMaxMinVolt();

    void printFuelGaugeInfo();

M module-bsp/board/rt1051/puretx/hal/battery_charger/BatteryCharger.cpp => module-bsp/board/rt1051/puretx/hal/battery_charger/BatteryCharger.cpp +9 -1
@@ 3,6 3,10 @@

#include "BatteryCharger.hpp"

#if ENABLE_CURRENT_MEASUREMENT_SCOPE
#include "CurrentMeasurementScope.hpp"
#endif

#include <bsp/battery_charger/battery_charger.hpp>
#include <hal/GenericFactory.hpp>



@@ 25,7 29,11 @@ namespace hal::battery
        : eventsHandler(eventsHandler),
          timerHandle{xTimerCreate(
              "clearIrqStatusTimer", pdMS_TO_TICKS(clearStatusTickTime_MS), false, nullptr, clearIrqStatusHandler)}
    {}
    {
#if ENABLE_CURRENT_MEASUREMENT_SCOPE
        CurrentMeasurementScope::start();
#endif
    }

    void BatteryCharger::init(xQueueHandle queueBatteryHandle, xQueueHandle queueChargerDetect)
    {

A module-bsp/board/rt1051/puretx/hal/battery_charger/CurrentMeasurementScope.cpp => module-bsp/board/rt1051/puretx/hal/battery_charger/CurrentMeasurementScope.cpp +42 -0
@@ 0,0 1,42 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "CurrentMeasurementScope.hpp"

extern "C"
{
#include "FreeRTOS.h"
}
#include <timers.h>
#include <bsp/battery_charger/battery_charger.hpp>
#include <ticks.hpp>

#include <chrono>

namespace hal::battery::CurrentMeasurementScope
{
    namespace
    {
        using namespace std::chrono_literals;
        constexpr auto samplingTime = 100ms;
        TimerHandle_t samplingTimerHandle;

        static void getSample(TimerHandle_t xTimer)
        {
            LOG_DEBUG("[scope]: { \"timestamp\" : %ld, \"current\" : %d, \"current_filtered\" : %d  }",
                      cpp_freertos::Ticks::TicksToMs(cpp_freertos::Ticks::GetTicks()),
                      bsp::battery_charger::getCurrentMeasurement(),
                      bsp::battery_charger::getAvgCurrent());
        }
    } // namespace

    void start()
    {
        if (samplingTimerHandle == nullptr) {
            samplingTimerHandle =
                xTimerCreate("samplingTimer", pdMS_TO_TICKS(samplingTime.count()), true, nullptr, getSample);
        }
        xTimerStart(samplingTimerHandle, 0);
    }

} // namespace hal::battery::CurrentMeasurementScope

A module-bsp/board/rt1051/puretx/hal/battery_charger/CurrentMeasurementScope.hpp => module-bsp/board/rt1051/puretx/hal/battery_charger/CurrentMeasurementScope.hpp +9 -0
@@ 0,0 1,9 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

namespace hal::battery::CurrentMeasurementScope
{
    void start();
} // namespace hal::battery::CurrentMeasurementScope

A tools/plot_current_measurement.py => tools/plot_current_measurement.py +130 -0
@@ 0,0 1,130 @@
#!/usr/bin/python3
# Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

'''
Tool for plotting realtime data provided through RTT via JLink.
Data must be wrapped in string containing json with timestamp, current and current_filtered keywords.
Specific line is detected by searching for SCOPE_KEYWORD. All other logs are printed in console.

Usage:
    1. Before running the script JLinkServer must be running
    2. Max number of displayed samples is specified by MAX_SAMPLES_TO_DISPLAY
    3. Plot refresh rate is specified by REFRESH_INTERVAL_MS
    4. To log data into CSV file please provide --save_to_csv FILENAME
'''

import pylink
import sys
import time
import json
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
import collections 
import argparse
import csv

try:
    import thread
except ImportError:
    import _thread as thread

# Plot constants
MAX_SAMPLES_TO_DISPLAY = 200
REFRESH_INTERVAL_MS = 100

# Parser constants
SCOPE_KEYWORD = '[scope]: '

ts = collections.deque(np.zeros(MAX_SAMPLES_TO_DISPLAY))
meas = collections.deque(np.zeros(MAX_SAMPLES_TO_DISPLAY))
meas_flt = collections.deque(np.zeros(MAX_SAMPLES_TO_DISPLAY))
fig = plt.figure(figsize=(12,6))
ax = plt.subplot()
csv_filename = str()

def save_to_csv(to_save):
    # save to csv if filename provided
    if csv_filename:
        with open(csv_filename, 'a') as file:
            writer = csv.writer(file)
            writer.writerow(to_save)

def parse_lines(charbuf):
    for line in charbuf.splitlines():
        if SCOPE_KEYWORD in line:
            try:
                to_parse = line.split(SCOPE_KEYWORD)[1]
                json_meas = json.loads(to_parse)

                ts.popleft()
                ts.append(int(json_meas['timestamp']))
                meas.popleft()
                meas.append(int(json_meas['current']))
                meas_flt.popleft()
                meas_flt.append(int(json_meas['current_filtered']))

                save_to_csv([int(json_meas['timestamp']), int(json_meas['current']), int(json_meas['current_filtered'])])
            except:
                print("exception in json parse, corrupted data")
        else:
            print(line) # print other logs in console

def read_rtt(jlink):
    try:
        while jlink.connected():
            terminal_bytes = jlink.rtt_read(0, 1024)
            if terminal_bytes:
                parse_lines("".join(map(chr, terminal_bytes)))
            time.sleep(0.01)
    except Exception:
        print("IO read thread exception, exiting...")
        thread.interrupt_main()
        raise

def animation(i):
    try:
        ax.cla()
        num_of_elems = int(np.nonzero(ts)[0][0]) # find first nonzero element in ts queue
        ax.plot(ts, meas, 'b')
        ax.plot(ts, meas_flt, 'g')

        # plot styling
        plt.title("Current measurement - b: inst, g: filtered", loc = 'left')
        plt.xlabel("Timestamp [ms]")
        plt.ylabel("Current [mA]")
        plt.xlim([ts[num_of_elems], ts[-1]])
        plt.grid(True)
    except:
        print("exception in plotter - no data provided")

def main():
    jlink = pylink.JLink()
    print("connecting to JLink...")
    jlink.open()
    print("starting RTT...")
    jlink.rtt_start()

    ani = FuncAnimation(fig, animation, interval=REFRESH_INTERVAL_MS)
    
    try:
        thread.start_new_thread(read_rtt, (jlink,))
        plt.show()
        while jlink.connected():
            time.sleep(1)
        print("JLink disconnected, exiting...")
    except KeyboardInterrupt:
        print("ctrl-c detected, exiting...")
        pass

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="RTT realtime plotter")
    parser.add_argument("--save_to_csv",
        help="Save data to csv file. Provide filename as an argument.",
        default='')

    args = parser.parse_args()
    csv_filename = args.save_to_csv

    sys.exit(main())

M tools/requirements.txt => tools/requirements.txt +2 -0
@@ 8,3 8,5 @@ smmap==4.0.0
tqdm==4.62.2
urllib3==1.26.6
ghapi
pylink
matplotlib