diff --git a/tests/driver_ltc4150/Makefile b/tests/driver_ltc4150/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..b891f0b130858c1bf53030d2efaee3754a5df61f --- /dev/null +++ b/tests/driver_ltc4150/Makefile @@ -0,0 +1,8 @@ +include ../Makefile.tests_common + +BOARD ?= msba2 + +USEMODULE += fmt +USEMODULE += ltc4150 + +include $(RIOTBASE)/Makefile.include diff --git a/tests/driver_ltc4150/main.c b/tests/driver_ltc4150/main.c new file mode 100644 index 0000000000000000000000000000000000000000..12ced98827a9799f39f8c2fe258d3cbb6a91c0a5 --- /dev/null +++ b/tests/driver_ltc4150/main.c @@ -0,0 +1,331 @@ +/* + * 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 <marian.buschsieweke@ovgu.de> + * + * @} + */ + +#include <errno.h> +#include <stdio.h> + +#include "fmt.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 the given number of spaces + */ +static void print_spaces(size_t number) +{ + static const char *spaces = " "; + while (number > 16) { + fputs(spaces, stdout); + number -= 16; + } + + fputs(spaces + 16 - number, stdout); +} + +/** + * @brief Print a table column with the given number as decimal + * @param number Number to print in the column + * @param width Width of the column + */ +static void print_col_u32(uint32_t number, size_t width) +{ + char sbuf[32]; + size_t slen; + + slen = fmt_u32_dec(sbuf, number); + sbuf[slen] = '\0'; + if (width > slen) { + print_spaces(width - slen); + } + fputs(sbuf, stdout); +} + +/** + * @brief Print a table column with the given number as decimal + * @param number Number to print in the column + * @param width Width of the column + */ +static void print_col_i32(int32_t number, size_t width) +{ + char sbuf[32]; + size_t slen; + char *pos = sbuf; + + if (number < 0) { + *pos++ = '-'; + number = -number; + width--; + } + slen = fmt_u32_dec(sbuf, (uint32_t)number); + sbuf[slen] = '\0'; + if (width > slen) { + print_spaces(width - slen); + } + fputs(sbuf, stdout); +} + +/** + * @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[3]; + + print_col_i32(current/10, width - 2); + sbuf[0] = '.'; + sbuf[1] = '0' + current % 10; + sbuf[2] = '\0'; + fputs(sbuf, stdout); +} + +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) { + fputs("Failed to initialize LTC4150 driver:", stdout); + switch (retval) { + case -EINVAL: + puts("Invalid parameter"); + break; + case -EIO: + puts("GPIO or interrupt configuration failed"); + break; + default: + puts("Unknown (should no happen, file a bug)"); + 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"); + + puts("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."); + + LED0_OFF; + + puts("+-------------------------------+-----------------------------------+\n" + "| Total Transferred Charge [mC] | Current from Power Supply [mA] |\n" + "| Charging | Discharging | Average | Last Minute | Currently |\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) { + puts("+---------------+---------------+---------+-------------+-----------+"); + change_of_load_level = 0; + } + + /* Get & print total charge transferred */ + if (ltc4150_charge(<c4150, &charged, &discharged)) { + puts("ltc4150_charge() failed!"); + return -1; + } + fputs("| ", stdout); + print_col_u32(charged, 13); + fputs(" | ", stdout); + print_col_u32(discharged, 13); + fputs(" | ", stdout); + + /* Get & print avg current */ + if (ltc4150_avg_current(<c4150, &avg_current)) { + puts("ltc4150_avg_current() failed!"); + return -1; + } + print_current(avg_current, 7); + fputs(" | ", stdout); + + /* Get & print last minute current */ + if (ltc4150_last_minute_charge(<c4150, &last_minute_data, + &charged, &discharged) + ) { + puts("ltc4150_last_minute_charge() failed!"); + return -1; + } + current = (int32_t)discharged - (int32_t)charged; + current /= 60; + print_col_i32(current, 11); + fputs(" | ", stdout); + + /* 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); + puts(" |"); + } + + return 0; +}