|
rcbench
Servo and ESC test bench: panel, IOmcu and shared C
|
Writes a run as CSV rows to a sink, see log_writer.h. More...
#include "log_writer.h"#include "supply.h"#include <math.h>#include <stdio.h>#include <string.h>Macros | |
| #define | LOG_SERVO_NO_ANGLE 0x8000u |
| The packed angle that is no angle (log_servo_pack()). | |
Enumerations | |
| enum | { RAIL_NONE = 0 , RAIL_V , RAIL_I , RAIL_P , RAIL_Q , RAIL_E } |
| The supply figures of a SERVO run's row, by column (log_rail_t). More... | |
Functions | |
| static const char * | source_word (bool modelled) |
| The last column of both files: what produced the row's values. | |
| static bool | put (log_writer_t *w, const char *s, size_t n) |
| A short write latches the writer, and the bytes the sink took stay in the file: nothing takes them back out of a card. | |
| void | log_writer_init (log_writer_t *w, const log_sink_t *sink) |
| Sets up a writer on a sink. | |
| static bool | commit (log_writer_t *w) |
| Tell the sink to keep what it has been given. | |
| bool | log_writer_commit (log_writer_t *w) |
| Commit the rows written since the last commit. | |
| void | log_writer_set_cols (log_writer_t *w, const log_cols_t *cols) |
| The INA3221 columns of this file, before its header: with SENSE_MAP in force a group per watched target (log_cols.h), read from the window bench_state_t.chw carries; NULL for SERVO_SENSE's CH1 to CH3, as an IOmcu older than 4.25 has them. | |
| static bool | header (log_writer_t *w, bool servo) |
The header of a bench file: a motor run's, or with servo a SERVO run's, which adds k_servo_cols before source. | |
| bool | log_writer_header (log_writer_t *w) |
| Write the header row. | |
| static int | fmt (char *out, size_t cap, float v, int decimals) |
| A reading that is not finite is written as an empty cell, which the reader counts as absent. | |
| static bool | finish_row (log_writer_t *w, float t_s, char *line, int n) |
| The end of every row, whichever kind: the line goes out, the counts move, and the commit comes on rows or on the run's own clock, whichever is first. | |
| static bool | cell (char *line, size_t cap, int *n, bool present, float v, int decimals) |
One cell of a row: the separator, then the value if present. | |
| static bool | text_cell (char *line, size_t cap, int *n, const char *text) |
| Appends one text cell to a row. | |
| bool | log_writer_supply_row (log_writer_t *w, float t_s, const supply_state_t *s) |
| Append one supply sample, for a run that is the supply's output being on. | |
| static bool | rail_figure (const log_rail_t *r, unsigned which, float *v) |
The cell of a supply figure on a SERVO run's row (RAIL_*), from r: false when r has no reading or total of it. | |
| static bool | bench_row (log_writer_t *w, float t_s, const bench_state_t *b, bool servo, const log_rail_t *rail, const log_servo_t *sv) |
One bench row, a motor run's with servo false and a SERVO run's with it true: then the five columns k_cols marks are rail's. | |
| bool | log_writer_row (log_writer_t *w, float t_s, const bench_state_t *b) |
Append one sample at t_s seconds, and commit when the row completes an interval. | |
| bool | log_writer_servo_row (log_writer_t *w, float t_s, const bench_state_t *b, const log_rail_t *rail, const log_servo_t *sv) |
Append one sample of a run ARMED from SERVO: log_writer_row()'s row with voltage, current, power, charge and energy from rail. | |
| void | log_rail_take_ina (log_rail_t *out, const sense_link_win_t *win, bool row_modelled) |
The servo rail's INA3221 window for a SERVO run's row, into out, with nothing counted: while an INA3221 channel is the rail's meter (servo_source.h). | |
| uint32_t | log_servo_pack (bool pulse_ok, uint16_t pulse_us, bool angle_ok, float angle_deg) |
| The pulse and the angle in one 32-bit word, for the render side to hand the control task in one atomic store. | |
| void | log_servo_unpack (uint32_t packed, bool row_modelled, log_servo_t *out) |
log_servo_pack()'s word back into out, for a row: no angle on a modelled row, whose figures the encoder did not produce. | |
| void | log_rail_take (log_rail_t *out, const supply_state_t *s, bool row_modelled) |
The supply's reading for a SERVO run's row, into out, with nothing counted. | |
| void | log_rail_count (bench_totals_t *t, log_rail_t *r, float dt_s, bool driving) |
Count r's readings into the run's totals t over dt_s, and put the totals on r. | |
Variables | |
| static const char *const | k_head |
| static const char *const | k_ch123 |
| static const char *const | k_tail = "source\n" |
| static const char *const | k_supply_header |
| static const char *const | k_servo_cols = "pulse (us);angle (deg);" |
| A SERVO run's file: the commanded pulse and the measured angle, before source. | |
Writes a run as CSV rows to a sink, see log_writer.h.
SPDX-License-Identifier: MIT
| anonymous enum |
The supply figures of a SERVO run's row, by column (log_rail_t).
| Enumerator | |
|---|---|
| RAIL_NONE | a column the bench sample fills |
| RAIL_V | voltage |
| RAIL_I | current |
| RAIL_P | power |
| RAIL_Q | charge |
| RAIL_E | energy |
|
static |
One bench row, a motor run's with servo false and a SERVO run's with it true: then the five columns k_cols marks are rail's.
| w | The writer. |
| t_s | The time of the row, in s. |
| b | The bench sample. |
| servo | true for a SERVO run's row. |
| rail | The supply's figures on a SERVO run's row, or NULL. |
| sv | The pulse and angle on a SERVO run's row, or NULL. |
|
static |
One cell of a row: the separator, then the value if present.
False once the row no longer fits, checked before anything is written past what the last cell left: snprintf answers the length it wanted, not the one it had, so an offset taken from it unchecked points past the line.
| line | The row text. |
| cap | The size of line, in bytes. |
| n | The length of the row so far; updated. |
| present | true when v is written. |
| v | The value. |
| decimals | The digits after the point. |
|
static |
Tell the sink to keep what it has been given.
A writer that has already failed is refused before anything is attempted. The file has a hole in it from the row that failed, and a flush that succeeds after that would clear the pending count and report a commit – which is a caller being told an incomplete file is safely on the card. Once failed, always failed; the file is closed and said to be short.
Nothing to commit is success, not a transaction: on a card an empty commit costs a directory write and buys nothing.
| w | The writer. |
|
static |
The end of every row, whichever kind: the line goes out, the counts move, and the commit comes on rows or on the run's own clock, whichever is first.
The clock bound is what holds when rows arrive slower than 20Hz – the far end answering every other poll, say – where waiting for LOG_WRITER_FLUSH_ROWS would leave more than a second of run at risk.
| w | The writer. |
| t_s | The time of the row, in s. |
| line | The row text. |
| n | The length of line, in bytes. |
|
static |
A reading that is not finite is written as an empty cell, which the reader counts as absent.
"nan" is not a number to the reader, and 0 is a wrong value.
| out | Receives the text. |
| cap | The size of out, in bytes. |
| v | The reading. |
| decimals | The digits after the point. |
|
static |
The header of a bench file: a motor run's, or with servo a SERVO run's, which adds k_servo_cols before source.
| w | The writer. |
| servo | true for a SERVO run's file. |
| void log_rail_count | ( | bench_totals_t * | t, |
| log_rail_t * | r, | ||
| float | dt_s, | ||
| bool | driving ) |
Count r's readings into the run's totals t over dt_s, and put the totals on r.
Charge from a current reading, energy from a power reading, as bench_totals_count() counts them: a step over BENCH_TOTALS_MAX_STEP_S (1.0s) counts 1.0s, a step that is not positive counts nothing, and nothing is counted unless driving. bench_totals_reset() starts a run. A withheld reading counts nothing and takes no totals: its row carries none, the model's totals of an earlier stretch included. Either pointer NULL does nothing.
| t | The run's totals. |
| r | The reading; its charge_mah, energy_wh and counted are set. |
| dt_s | The time since the last count, in s. |
| driving | true while the bank is ARMED. |
| void log_rail_take | ( | log_rail_t * | out, |
| const supply_state_t * | s, | ||
| bool | row_modelled ) |
The supply's reading for a SERVO run's row, into out, with nothing counted.
The readings that arrived, by s's ok bits, when the supply's source is the row's: a supply that is the panel's model (supply_state_t.modelled) on a modelled row, a measured one on a measured row. Otherwise none, and out->withheld says so: a row's source names all its figures. s NULL takes none.
| out | Receives the reading. |
| s | The supply's state, or NULL. |
| row_modelled | true when the row is the panel's model (bench_state_simulated()). |
| void log_rail_take_ina | ( | log_rail_t * | out, |
| const sense_link_win_t * | win, | ||
| bool | row_modelled ) |
The servo rail's INA3221 window for a SERVO run's row, into out, with nothing counted: while an INA3221 channel is the rail's meter (servo_source.h).
The window's mean bus voltage, its mean current and their product, as SERVO's line shows them, each by the window's current and voltage flags. None on a modelled row, and out->withheld says so: the part reads through the IOmcu. win NULL takes none.
| out | Receives the reading. |
| win | The newest window of the rail's channel, or NULL. |
| row_modelled | true when the row is the panel's model. |
| uint32_t log_servo_pack | ( | bool | pulse_ok, |
| uint16_t | pulse_us, | ||
| bool | angle_ok, | ||
| float | angle_deg ) |
The pulse and the angle in one 32-bit word, for the render side to hand the control task in one atomic store.
The pulse in bits 16 to 31, 0 for none; the angle in centidegrees, signed, in bits 0 to 15, 0x8000 for none. An angle that is not finite or lies past +-327.67deg packs as none, and so does a pulse of 0.
| pulse_ok | true when a pulse is commanded. |
| pulse_us | The commanded pulse, us. |
| angle_ok | true when an angle was measured. |
| angle_deg | The angle, deg. |
| void log_servo_unpack | ( | uint32_t | packed, |
| bool | row_modelled, | ||
| log_servo_t * | out ) |
log_servo_pack()'s word back into out, for a row: no angle on a modelled row, whose figures the encoder did not produce.
| packed | The word. |
| row_modelled | true when the row is the panel's model. |
| out | Receives the pulse and angle; NULL does nothing. |
| bool log_writer_commit | ( | log_writer_t * | w | ) |
Commit the rows written since the last commit.
log_writer_row() calls it on the schedule above. A caller calls it by hand when a run has gone quiet with rows still uncommitted, and at the end of a run when closing the sink does not commit by itself.
A commit with nothing pending does nothing and reports success: on a card it would be a transaction that buys nothing. A commit that fails latches the writer the way a failed write does, because a row the sink will not keep is a row the file has lost.
| w | The writer. |
| bool log_writer_header | ( | log_writer_t * | w | ) |
Write the header row.
log_writer_row calls it if it has not been called, so a file cannot lack a header.
| w | The writer. |
| void log_writer_init | ( | log_writer_t * | w, |
| const log_sink_t * | sink ) |
Sets up a writer on a sink.
| w | The writer to set up. |
| sink | The sink the rows go to. |
| bool log_writer_row | ( | log_writer_t * | w, |
| float | t_s, | ||
| const bench_state_t * | b ) |
Append one sample at t_s seconds, and commit when the row completes an interval.
False means one of two things, and log_writer_failed() tells them apart:
A write or the commit after it failed. log_writer_failed() is true and stays true, and every later row is refused: the file has a hole in it and no amount of card answering again makes it whole.
The arguments were refused – w or b NULL, or t_s not finite. Nothing was attempted and nothing is latched. That is a caller with a bug rather than a card that has gone, and ending a run over it would throw away a recording for a reason the card had no part in.
So a caller that watches only the latch has to check the return as well if it can pass either of those. The panel cannot: its row carries a struct by value and a finite time: a supply run's sum of steps, or a bench run's wall time since the arm (log_cadence.h).
| w | The writer. |
| t_s | The time of the row, in s. |
| b | The sample to write. |
| bool log_writer_servo_row | ( | log_writer_t * | w, |
| float | t_s, | ||
| const bench_state_t * | b, | ||
| const log_rail_t * | rail, | ||
| const log_servo_t * | sv ) |
Append one sample of a run ARMED from SERVO: log_writer_row()'s row with voltage, current, power, charge and energy from rail.
The header is the motor run's with pulse (us);angle (deg) before source, so both files read by one rule. The supply's cells are written as a supply file writes them: voltage and power to 0.01, current to 0.001, charge to 1mAh, energy to 0.01Wh; a cell rail has no reading or total of is empty, and so are all five with rail NULL. rpm, the temperatures, the INA228's and the ESC's columns, the INA3221 window and source are b's, as log_writer_row() writes them. The pulse is written in whole us and the angle to 0.1deg, each empty when sv has none, and both with sv NULL. Same returns, commits and latch as log_writer_row().
| w | The writer. |
| t_s | The time of the row, in s. |
| b | The bench sample. |
| rail | The supply's reading and totals, or NULL. |
| sv | The pulse and angle, or NULL. |
| void log_writer_set_cols | ( | log_writer_t * | w, |
| const log_cols_t * | cols ) |
The INA3221 columns of this file, before its header: with SENSE_MAP in force a group per watched target (log_cols.h), read from the window bench_state_t.chw carries; NULL for SERVO_SENSE's CH1 to CH3, as an IOmcu older than 4.25 has them.
Taken until the header is written, ignored after.
| w | The writer. |
| cols | The INA3221 columns, or NULL. |
| bool log_writer_supply_row | ( | log_writer_t * | w, |
| float | t_s, | ||
| const supply_state_t * | s ) |
Append one supply sample, for a run that is the supply's output being on.
The file's first row writes the supply header – time; set (V); voltage (V); limit (A); current (A); power (W); mode; charge (mAh); energy (Wh); source – so a file is one kind or the other and the caller keeps it so. Readings that did not arrive, and totals nothing counted, are empty cells; the mode is CV, CC or OFF, and empty while the supply does not answer. Same returns, commits and latch as log_writer_row().
| w | The writer. |
| t_s | The time of the row, in s. |
| s | The supply sample. |
|
static |
A short write latches the writer, and the bytes the sink took stay in the file: nothing takes them back out of a card.
Nothing is written after them either, a line feed least of all. The row they start has no line feed, and that is what the reader drops it by (log_reader_row()): a row cut after "95" of "9554" has its fields and a number that parses, and only the missing terminator tells it from a whole row.
| w | The writer. |
| s | The bytes. |
| n | The number of bytes. |
|
static |
The cell of a supply figure on a SERVO run's row (RAIL_*), from r: false when r has no reading or total of it.
| r | The supply's reading and totals, or NULL. |
| which | RAIL_V to RAIL_E. |
| v | Receives the figure when true. |
|
static |
The last column of both files: what produced the row's values.
| modelled | true when the row's values are modelled. |
|
static |
Appends one text cell to a row.
| line | The row text. |
| cap | The size of line, in bytes. |
| n | The length of the row so far; updated. |
| text | The text. |
|
static |
|
static |
|
static |