/* * Copyright (C) 2019 Otto-von-Guericke-Universität Magdeburg * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level * directory for more details. */ /** * @ingroup tests * @{ * * @file * @brief Test application for the LTC4150 coulomb counter driver * * @author Marian Buschsieweke * * @} */ #include #include "fmt.h" #include "fmt_table.h" #include "led.h" #include "ltc4150.h" #include "thread.h" #include "xtimer.h" typedef struct { uint64_t last_usec; uint64_t now_usec; ltc4150_dir_t dir; } test_recorder_data_t; static void pulse_cb(ltc4150_dev_t *, ltc4150_dir_t, uint64_t, void *); static void reset_cb(ltc4150_dev_t *, uint64_t, void *); static ltc4150_last_minute_data_t last_minute_data; static test_recorder_data_t test_data; static const ltc4150_recorder_t test_recorder = { .pulse = pulse_cb, .reset = reset_cb, }; static kernel_pid_t target_pid; static char busy_thread_stack[THREAD_STACKSIZE_DEFAULT]; static ltc4150_dev_t ltc4150; static int change_of_load_level = 0; static const ltc4150_recorder_t *recorders[] = { <c4150_last_minute, &test_recorder, NULL }; static void *recorder_data[] = { &last_minute_data, &test_data, }; #define LTC4150_PARAM_RECS (recorders) #define LTC4150_PARAM_RECDATA (recorder_data) #include "ltc4150_params.h" /** * @brief Callback function to reset/initialize the recorder data */ static void reset_cb(ltc4150_dev_t *dev, uint64_t now_usec, void *_data) { (void)dev; test_recorder_data_t *data = _data; data->last_usec = data->now_usec = now_usec; data->dir = LTC4150_DISCHARGE; } /** * @brief Callback function to record the current pulse */ static void pulse_cb(ltc4150_dev_t *dev, ltc4150_dir_t dir, uint64_t now_usec, void *_data) { (void)dev; static msg_t m = { .content = { .value = 0} }; test_recorder_data_t *data = _data; data->last_usec = data->now_usec; data->now_usec = now_usec; data->dir = dir; msg_send(&m, target_pid); } /** * @brief Busy waits for the given amount of seconds * @param seconds Number of seconds to roast the CPU */ static void spin(uint32_t seconds) { uint32_t till = xtimer_now_usec() + US_PER_SEC * seconds; while (xtimer_now_usec() < till) { } } /** * @brief Thread that will put three levels of CPU load on the MCU */ static void *busy_thread(void *arg) { (void)arg; while (1) { /* one minute of ~0% CPU usage */ LED0_OFF; LED1_OFF; xtimer_sleep(60); change_of_load_level = 1; /* one minute of ~50% CPU usage */ for (unsigned i = 0; i < 30; i++) { LED0_OFF; LED1_OFF; xtimer_sleep(1); LED0_ON; LED1_ON; spin(1); } change_of_load_level = 1; /* one minute of 100% CPU usage */ LED0_ON; LED1_ON; spin(60); change_of_load_level = 1; } /* unreachable */ return NULL; } /** * @brief Print a table column with the given current as E-01 * @param current Value to print in the column (as E-01) * @param width Width of the column */ static void print_current(int32_t current, size_t width) { char sbuf[2]; print_col_s32_dec(current/10, width - 2); sbuf[0] = '.'; sbuf[1] = '0' + current % 10; print(sbuf, 2); } int main(void) { target_pid = thread_getpid(); uint32_t ten_uc_per_pulse; msg_t m; int retval; retval = ltc4150_init(<c4150, <c4150_params[0]); /* Pre-compute the charge corresponding to one pulse */ ltc4150_pulses2c(<c4150, &ten_uc_per_pulse, NULL, 10000, 0); if (retval) { print_str("Failed to initialize LTC4150 driver:"); switch (retval) { case -EINVAL: print_str("Invalid parameter\n"); break; case -EIO: print_str("GPIO or interrupt configuration failed\n"); break; default: print_str("Unknown (should no happen, file a bug)\n"); break; } return -1; } /* Start the thread that will keep the MCU busy */ thread_create(busy_thread_stack, sizeof(busy_thread_stack), THREAD_PRIORITY_MAIN + 1, THREAD_CREATE_STACKTEST, busy_thread, NULL, "busy_thread"); print_str("This test will put three levels of load on the MCU:\n" " 1. One minute of little to no load (LEDs(*) off)\n" " 2. One minute of about 50% CPU load (LEDs(*) blinking)\n" " 3. One minute of 100% CPU load (LEDs(*) constantly on)\n" "\n" " (*) LED0 and LED1, if present on your board\n" "\n" "During this time the charge drawn is measured and printed on every\n" "pulse the LTC4150 generates. A horizontal line in the table\n" "separates values of different load levels. On the MSB-A2 the\n" "expected result per column is:\n" "\n" " Charging: Should remain zero\n" " Discharging: Should increase for every pulse\n" " Average: Should be something between 60mA to 80mA\n" " Last Minute: Starts with 0 at boot up and is updated every 10s.\n" " Should be higher for higher system load when looking\n" " at the last update for each load level.\n" " (Note: Not synchronized with load levels!)\n" " Currently: Should be higher for higher system load. Might be\n" " \"jumpy\" on 50% load, as this implemented by having\n" " one second of 100% load and one second of ~0% load\n" " in turns.\n" "\n" "Hint: You'll want to look mostly at the rightmost column.\n" "Note: The test will repeat endlessly.\n"); LED0_OFF; print_str("+-------------------------------+-----------------------------------+\n" "| Total Transferred Charge [mC] | Current from Power Supply [mA] |\n" "| Charging | Discharging | Average | Last Minute | Currently |\n" "+---------------+---------------+---------+-------------+-----------+\n"); while (1) { /* Wait for the next pulse of the LTC4150 */ msg_receive(&m); uint32_t charged, discharged; int16_t avg_current; int32_t current; if (change_of_load_level) { print_str("+---------------+---------------+---------+-------------+-----------+\n"); change_of_load_level = 0; } /* Get & print total charge transferred */ if (ltc4150_charge(<c4150, &charged, &discharged)) { print_str("ltc4150_charge() failed!\n"); return -1; } print_str("| "); print_col_u32_dec(charged, 13); print_str(" | "); print_col_u32_dec(discharged, 13); print_str(" | "); /* Get & print avg current */ if (ltc4150_avg_current(<c4150, &avg_current)) { print_str("ltc4150_avg_current() failed!\n"); return -1; } print_current(avg_current, 7); print_str(" | "); /* Get & print last minute current */ if (ltc4150_last_minute_charge(<c4150, &last_minute_data, &charged, &discharged) ) { print_str("ltc4150_last_minute_charge() failed!\n"); return -1; } current = (int32_t)discharged - (int32_t)charged; current /= 60; print_col_s32_dec(current, 11); print_str(" | "); /* Calculate & print the current between the last two pulses */ current = (int32_t)((test_data.now_usec - test_data.last_usec) / MS_PER_SEC); current = ten_uc_per_pulse / current; if (test_data.dir == LTC4150_CHARGE) { current = -current; } print_current(current, 9); print_str(" |\n"); } return 0; }