diff --git a/quad/.gitattributes b/quad/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..16008a9770535114262510a59cf903c5218689e2 --- /dev/null +++ b/quad/.gitattributes @@ -0,0 +1,4 @@ +# Always use lf for c and header files +*.c eol=lf +*.h eol=lf + diff --git a/quad/ci-test.sh b/quad/ci-test.sh index bb2cc16665d0687f7f3cdb553e2d39e8c1ff1b9c..b1e40c7ef4c6424ba48aec4745aab5317ff982fa 100644 --- a/quad/ci-test.sh +++ b/quad/ci-test.sh @@ -11,3 +11,7 @@ make || exit 1 cd $QUAD_ROOT/sw/modular_quad_pid/test make || exit 1 ./test_uart_buff || exit 1 + +cd $QUAD_ROOT/computation_graph/test +make || exit 1 +./test_computation_graph || exit 1 diff --git a/quad/computation_graph/.gitignore b/quad/computation_graph/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..429b549db1ac8959e01fbdaf54012317473f58ee --- /dev/null +++ b/quad/computation_graph/.gitignore @@ -0,0 +1,4 @@ +bin/ +build/ +computation_graph +test/test_computation_graph diff --git a/quad/computation_graph/Makefile b/quad/computation_graph/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..44c7e592619900de5251a68e4a8acb989422a6fc --- /dev/null +++ b/quad/computation_graph/Makefile @@ -0,0 +1,220 @@ +#### PROJECT SETTINGS #### +# The name of the executable to be created +BIN_NAME := computation_graph +# Compiler used +CC ?= gcc +# Extension of source files used in the project +SRC_EXT = c +# Path to the source directory, relative to the makefile +SRC_PATH = ./src +# Space-separated pkg-config libraries used by this project +LIBS = +# General compiler flags +COMPILE_FLAGS = -std=c99 -Wall -Wextra -g +# Additional release-specific flags +RCOMPILE_FLAGS = -D NDEBUG +# Additional debug-specific flags +DCOMPILE_FLAGS = -D DEBUG +# Add additional include paths +INCLUDES = -I $(SRC_PATH) +# General linker settings +LINK_FLAGS = -lm +# Additional release-specific linker settings +RLINK_FLAGS = +# Additional debug-specific linker settings +DLINK_FLAGS = +# Destination directory, like a jail or mounted system +DESTDIR = / +# Install path (bin/ is appended automatically) +INSTALL_PREFIX = usr/local +#### END PROJECT SETTINGS #### + +# Generally should not need to edit below this line + +# Obtains the OS type, either 'Darwin' (OS X) or 'Linux' +UNAME_S:=$(shell uname -s) + +# Function used to check variables. Use on the command line: +# make print-VARNAME +# Useful for debugging and adding features +print-%: ; @echo $*=$($*) + +# Shell used in this makefile +# bash is used for 'echo -en' +SHELL = /bin/bash +# Clear built-in rules +.SUFFIXES: +# Programs for installation +INSTALL = install +INSTALL_PROGRAM = $(INSTALL) +INSTALL_DATA = $(INSTALL) -m 644 + +# Append pkg-config specific libraries if need be +ifneq ($(LIBS),) + COMPILE_FLAGS += $(shell pkg-config --cflags $(LIBS)) + LINK_FLAGS += $(shell pkg-config --libs $(LIBS)) +endif + +# Verbose option, to output compile and link commands +export V := false +export CMD_PREFIX := @ +ifeq ($(V),true) + CMD_PREFIX := +endif + +# Combine compiler and linker flags +release: export CFLAGS := $(CFLAGS) $(COMPILE_FLAGS) $(RCOMPILE_FLAGS) +release: export LDFLAGS := $(LDFLAGS) $(LINK_FLAGS) $(RLINK_FLAGS) +debug: export CFLAGS := $(CFLAGS) $(COMPILE_FLAGS) $(DCOMPILE_FLAGS) +debug: export LDFLAGS := $(LDFLAGS) $(LINK_FLAGS) $(DLINK_FLAGS) + +# Build and output paths +release: export BUILD_PATH := build/release +release: export BIN_PATH := bin/release +debug: export BUILD_PATH := build/debug +debug: export BIN_PATH := bin/debug +install: export BIN_PATH := bin/release + +# Find all source files in the source directory, sorted by most +# recently modified +ifeq ($(UNAME_S),Darwin) + SOURCES = $(shell find $(SRC_PATH) -name '*.$(SRC_EXT)' | sort -k 1nr | cut -f2-) +else + SOURCES = $(shell find $(SRC_PATH) -name '*.$(SRC_EXT)' -printf '%T@\t%p\n' \ + | sort -k 1nr | cut -f2-) +endif + +# fallback in case the above fails +rwildcard = $(foreach d, $(wildcard $1*), $(call rwildcard,$d/,$2) \ + $(filter $(subst *,%,$2), $d)) +ifeq ($(SOURCES),) + SOURCES := $(call rwildcard, $(SRC_PATH), *.$(SRC_EXT)) +endif + +# Set the object file names, with the source directory stripped +# from the path, and the build path prepended in its place +OBJECTS = $(SOURCES:$(SRC_PATH)/%.$(SRC_EXT)=$(BUILD_PATH)/%.o) +# Set the dependency files that will be used to add header dependencies +DEPS = $(OBJECTS:.o=.d) + +# Macros for timing compilation +ifeq ($(UNAME_S),Darwin) + CUR_TIME = awk 'BEGIN{srand(); print srand()}' + TIME_FILE = $(dir $@).$(notdir $@)_time + START_TIME = $(CUR_TIME) > $(TIME_FILE) + END_TIME = read st < $(TIME_FILE) ; \ + $(RM) $(TIME_FILE) ; \ + st=$$((`$(CUR_TIME)` - $$st)) ; \ + echo $$st +else + TIME_FILE = $(dir $@).$(notdir $@)_time + START_TIME = date '+%s' > $(TIME_FILE) + END_TIME = read st < $(TIME_FILE) ; \ + $(RM) $(TIME_FILE) ; \ + st=$$((`date '+%s'` - $$st - 86400)) ; \ + echo `date -u -d @$$st '+%H:%M:%S'` +endif + +# Version macros +# Comment/remove this section to remove versioning +USE_VERSION := false +# If this isn't a git repo or the repo has no tags, git describe will return non-zero +ifeq ($(shell git describe > /dev/null 2>&1 ; echo $$?), 0) + USE_VERSION := true + VERSION := $(shell git describe --tags --long --dirty --always | \ + sed 's/v\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)-\?.*-\([0-9]*\)-\(.*\)/\1 \2 \3 \4 \5/g') + VERSION_MAJOR := $(word 1, $(VERSION)) + VERSION_MINOR := $(word 2, $(VERSION)) + VERSION_PATCH := $(word 3, $(VERSION)) + VERSION_REVISION := $(word 4, $(VERSION)) + VERSION_HASH := $(word 5, $(VERSION)) + VERSION_STRING := \ + "$(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH).$(VERSION_REVISION)-$(VERSION_HASH)" + override CFLAGS := $(CFLAGS) \ + -D VERSION_MAJOR=$(VERSION_MAJOR) \ + -D VERSION_MINOR=$(VERSION_MINOR) \ + -D VERSION_PATCH=$(VERSION_PATCH) \ + -D VERSION_REVISION=$(VERSION_REVISION) \ + -D VERSION_HASH=\"$(VERSION_HASH)\" +endif + +# Standard, non-optimized release build +.PHONY: release +release: dirs +ifeq ($(USE_VERSION), true) + @echo "Beginning release build v$(VERSION_STRING)" +else + @echo "Beginning release build" +endif + @$(START_TIME) + @$(MAKE) all --no-print-directory + @echo -n "Total build time: " + @$(END_TIME) + +# Debug build for gdb debugging +.PHONY: debug +debug: dirs +ifeq ($(USE_VERSION), true) + @echo "Beginning debug build v$(VERSION_STRING)" +else + @echo "Beginning debug build" +endif + @$(START_TIME) + @$(MAKE) all --no-print-directory + @echo -n "Total build time: " + @$(END_TIME) + +# Create the directories used in the build +.PHONY: dirs +dirs: + @echo "Creating directories" + @mkdir -p $(dir $(OBJECTS)) + @mkdir -p $(BIN_PATH) + +# Installs to the set path +.PHONY: install +install: + @echo "Installing to $(DESTDIR)$(INSTALL_PREFIX)/bin" + @$(INSTALL_PROGRAM) $(BIN_PATH)/$(BIN_NAME) $(DESTDIR)$(INSTALL_PREFIX)/bin + +# Uninstalls the program +.PHONY: uninstall +uninstall: + @echo "Removing $(DESTDIR)$(INSTALL_PREFIX)/bin/$(BIN_NAME)" + @$(RM) $(DESTDIR)$(INSTALL_PREFIX)/bin/$(BIN_NAME) + +# Removes all build files +.PHONY: clean +clean: + @echo "Deleting $(BIN_NAME) symlink" + @$(RM) $(BIN_NAME) + @echo "Deleting directories" + @$(RM) -r build + @$(RM) -r bin + +# Main rule, checks the executable and symlinks to the output +all: $(BIN_PATH)/$(BIN_NAME) + @echo "Making symlink: $(BIN_NAME) -> $<" + @$(RM) $(BIN_NAME) + @ln -s $(BIN_PATH)/$(BIN_NAME) $(BIN_NAME) + +# Link the executable +$(BIN_PATH)/$(BIN_NAME): $(OBJECTS) + @echo "Linking: $@" + @$(START_TIME) + $(CMD_PREFIX)$(CC) $(OBJECTS) $(LDFLAGS) -o $@ + @echo -en "\t Link time: " + @$(END_TIME) + +# Add dependency files, if they exist +-include $(DEPS) + +# Source file rules +# After the first compilation they will be joined with the rules from the +# dependency files to provide header dependencies +$(BUILD_PATH)/%.o: $(SRC_PATH)/%.$(SRC_EXT) + @echo "Compiling: $< -> $@" + @$(START_TIME) + $(CMD_PREFIX)$(CC) $(CFLAGS) $(INCLUDES) -MP -MMD -c $< -o $@ + @echo -en "\t Compile time: " + @$(END_TIME) diff --git a/quad/computation_graph/src/computation_graph.c b/quad/computation_graph/src/computation_graph.c new file mode 100644 index 0000000000000000000000000000000000000000..5fcfd4c5c76ad7c221da927c410f45184c5c0578 --- /dev/null +++ b/quad/computation_graph/src/computation_graph.c @@ -0,0 +1,248 @@ +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include "computation_graph.h" + +#define GRAPH_MAX_DEPTH 20 +#define GRAPH_MAX_INPUTS 20 + +// Array to store input values for passing to the execute function of each node +static double exec_input_vals[GRAPH_MAX_INPUTS]; + +struct computation_graph *create_graph() { + struct computation_graph *the_graph = malloc(sizeof(struct computation_graph)); + if (!the_graph) {return NULL;} + the_graph->nodes = malloc(sizeof(struct graph_node)); + if (!the_graph->nodes) { return NULL; } + the_graph->n_nodes = 0; + the_graph->size = 1; + return the_graph; +} + +static void reset_node_rec(struct computation_graph* graph, int node_id, int depth) { + if (depth > GRAPH_MAX_DEPTH) { + return; + } + struct graph_node* node = &graph->nodes[node_id]; + // Don't reset nodes that are already connected to something else + // Don't reset nodes that have already been reset or discovered + if (node->n_children != 1 || node->processed_state != UNPROCESSED) { + return; + } + node->processed_state = DISCOVERED; + int input_id; + for (input_id = 0; input_id < node->type->n_inputs; input_id++) { + int src_node_id = node->input_srcs[input_id].controller_id; + if (src_node_id != -1) { + reset_node_rec(graph, src_node_id, depth + 1); + } + } + // Reset this node + if (node->type->reset != NULL) { + node->type->reset(node->state); + } + node->updated = 1; + node->processed_state = PROCESSED; +} + +int reset_node(struct computation_graph* graph, int node_id) { + if (node_id >= graph->n_nodes) { + return -1; + } + int i; + for (i = 0; i < graph->n_nodes; i++) { + graph->nodes[i].processed_state = UNPROCESSED; + } + reset_node_rec(graph, node_id, 0); + return 0; +} + +int graph_set_source(struct computation_graph *graph, + int dest_node_id, int dest_input, int src_node_id, int src_output) { + if (dest_node_id >= graph->n_nodes || src_node_id >= graph->n_nodes) { + return -1; + } + struct graph_node *dest_node = &graph->nodes[dest_node_id]; + struct graph_node *src_node = &graph->nodes[src_node_id]; + if (dest_input >= dest_node->type->n_inputs || src_output >= src_node->type->n_outputs) { + return -1; + } + + // If a previous source exists, remove one from its children count + int prev_src_id = dest_node->input_srcs[dest_input].controller_id; + if (prev_src_id != -1) { + graph->nodes[prev_src_id].n_children -= 1; + } + src_node->n_children += 1; // Count destination node as a child + dest_node->input_srcs[dest_input].controller_id = src_node_id; + dest_node->input_srcs[dest_input].controller_output = src_output; + dest_node->updated = 1; + reset_node(graph, src_node_id); + return 0; +} + +int graph_add_node(struct computation_graph *graph, + const char* name, + const struct graph_node_type *type, + void *state) { + assert(type->n_inputs <= GRAPH_MAX_INPUTS); + int new_id = graph->n_nodes; + if (new_id >= graph->size) { + int new_capacity = graph->n_nodes == 0 ? 1 : graph->n_nodes * 2; + struct graph_node *node_arr = realloc(graph->nodes, sizeof(struct graph_node) * new_capacity); + if (!node_arr) { + return -1; + } + graph->size = new_capacity; + graph->nodes = node_arr; + } + struct graph_node *new_node = &graph->nodes[new_id]; + new_node->name = strdup(name); + new_node->type = type; + new_node->state = state; + new_node->n_children = 0; + new_node->updated = 1; + new_node->output_values = malloc(type->n_outputs * sizeof(double)); + new_node->param_values = malloc(type->n_params * sizeof(double)); + new_node->input_srcs = malloc(type->n_inputs * sizeof(struct input_type)); + // Check that malloc succeeded in every case which memory was requested + if ((type->n_outputs && !new_node->output_values) || + (type->n_params && !new_node->param_values) || + (type->n_inputs && !new_node->input_srcs)) { + return -1; + } + int i; + for (i = 0; i < type->n_inputs; i++) { + new_node->input_srcs[i].controller_id = -1; + } + graph->n_nodes += 1; + // Reset block upon creation + if (new_node->type->reset != NULL) { + new_node->type->reset(new_node->state); + } + return new_id; +} + +int graph_set_param_val(struct computation_graph *graph, int node_id, int param_id, double value) { + if (node_id >= graph->n_nodes || param_id >= graph->nodes[node_id].type->n_params) { + return -1; + } + graph->nodes[node_id].param_values[param_id] = value; + graph->nodes[node_id].updated = 1; + return 0; +} + +double graph_get_param_val(const struct computation_graph *graph, int node_id, int param_id) { + if (node_id >= graph->n_nodes || param_id >= graph->nodes[node_id].type->n_params) { + return NAN; + } + return graph->nodes[node_id].param_values[param_id]; +} + +double graph_get_output(const struct computation_graph *graph, int node_id, int output_id) { + if (node_id >= graph->n_nodes || output_id >= graph->nodes[node_id].type->n_outputs) { + return NAN; + } + return graph->nodes[node_id].output_values[output_id]; +} + +void graph_compute_node_rec(struct computation_graph *graph, int node_id, int depth) { + if (depth >= GRAPH_MAX_DEPTH) { + assert(1 == 0); // TODO :Xil_Assert false + return; + } + if (node_id >= graph->n_nodes) { + return; + } + struct graph_node *node = &graph->nodes[node_id]; + if (node->processed_state != UNPROCESSED) { + return; + } + node->processed_state = DISCOVERED; + int input_id; + for (input_id = 0; input_id < node->type->n_inputs; input_id++) { + int src_cntl_id = node->input_srcs[input_id].controller_id; + if (src_cntl_id != -1) { + graph_compute_node_rec(graph, src_cntl_id, depth + 1); + node->updated |= graph->nodes[src_cntl_id].updated; + } + } + if (node->updated) { + // Populate the exec_input_vals array for computation + for (input_id = 0; input_id < node->type->n_inputs; input_id++) { + int src_cntl_id = node->input_srcs[input_id].controller_id; + int src_output_id = node->input_srcs[input_id].controller_output; + if (src_cntl_id != -1) { + exec_input_vals[input_id] = graph->nodes[src_cntl_id].output_values[src_output_id]; + } + else { + // Set input value to 0 if not connected + exec_input_vals[input_id] = 0; + } + } + if (node->type->execute != NULL) { + (*node->type->execute)(node->state, node->param_values, exec_input_vals, node->output_values); + } + } + node->processed_state = PROCESSED; +} + +void graph_compute_nodes(struct computation_graph *graph, int* node_ids, int n_nodes) { + int i; + for (i = 0; i < graph->n_nodes; i++) { + graph->nodes[i].processed_state = UNPROCESSED; + } + for (i = 0; i < n_nodes; i++) { + int node_id = node_ids[i]; + if (node_id < graph->n_nodes) { + graph_compute_node_rec(graph, node_id, 0); + } + } + // Clear all the updated flags for nodes that were actually executed + for (i = 0; i < graph->n_nodes; i++) { + struct graph_node* node = &graph->nodes[i]; + if (node->processed_state == PROCESSED) { + node->updated = 0; + } + } +} + +int export_dot(const struct computation_graph* graph, FILE* of, int print_outputs) { + fprintf(of, "digraph G {\n"); // Header + fprintf(of, "rankdir=\"LR\"\n"); // Horizontal layout + + // Draw all the nodes and their inputs + int i; + for (i = 0; i < graph->n_nodes; i++) { + struct graph_node *node = &graph->nodes[i]; + // Create node + fprintf(of, "\"%s\"[shape=record\nlabel=\"", node->name); + fprintf(of, "<f0>%s ", node->name); // Node name is port 0 + int j; + // Create ports for inputs + for (j = 0; j < node->type->n_inputs; j++) { + fprintf(of, " |<f%d> --\\>%s", j+1, node->type->input_names[j]); + } + // Create ports for parameters + for (j = 0; j < node->type->n_params; j++) { + fprintf(of, " |<f%d> [%s=%.3f]", j+1+node->type->n_inputs, node->type->param_names[j],node->param_values[j]); + } + fprintf(of, "\"]\n"); // Close label bracket + // Make connections from + for (j = 0; j < node->type->n_inputs; j++) { + int input_id = node->input_srcs[j].controller_id; + if (input_id != -1) { + struct graph_node* src_node = &graph->nodes[input_id]; + int output_id = node->input_srcs[j].controller_output; + const char* output_name = src_node->type->output_names[output_id]; + fprintf(of, "\"%s\" -> \"%s\":f%d [label=\"%s", src_node->name, node->name, j+1, output_name); + if (print_outputs) { + fprintf(of, "=%.3f", src_node->output_values[output_id]); + } + fprintf(of, "\"]\n"); + } + } + } + fprintf(of, "}"); // Close graph + return 0; +} diff --git a/quad/computation_graph/src/computation_graph.h b/quad/computation_graph/src/computation_graph.h new file mode 100644 index 0000000000000000000000000000000000000000..e5e769fd28abcb90f66ef2df40434e132c496c62 --- /dev/null +++ b/quad/computation_graph/src/computation_graph.h @@ -0,0 +1,111 @@ +#ifndef __COMPUTATION_GRAPH_H__ +#define __COMPUTATION_GRAPH_H__ + +#include <stdio.h> +#include <math.h> + +typedef void (*execute_node_t)(void *state, + const double* params, + const double *inputs, + double *outputs); + +typedef void (*reset_node_t)(void *state); + +enum node_processed_state { + UNPROCESSED, + DISCOVERED, + PROCESSED +}; + +struct computation_graph { + int n_nodes; + int size; + struct graph_node *nodes; +}; + +// Declares a node type +struct graph_node_type { + const char* const* input_names; + const char* const* output_names; + const char* const* param_names; + int n_inputs; + int n_outputs; + int n_params; + execute_node_t execute; + reset_node_t reset; +}; + +// Declares an instance of a node +struct graph_node { + const char *name; // Name of this node instance + const struct graph_node_type* type; // Type of this node + double *output_values; // Computed output values + double *param_values; // Values of parameters set for this node + int n_children; // The number of connected children + void *state; // Pointer to the state instance + int processed_state; // State of the node with respecct to the graph traversal + struct input_type { // Array of tuples indicating the source for each input to this node + int controller_id; + int controller_output; + } *input_srcs; + int updated; // 1 if this node has had an input or parameter change +}; + +/* + * Creates an empty computation graph + * May return NULL on failure + */ +struct computation_graph *create_graph(); + +/* + * Defines which node's output gets its value passed into the input of a different node. + * Will call reset for each node which was previously orphaned, but is now connected to the graph + * dest_node_id: The ID of the node to which the input belongs + * dest_input: The ID of the input for node dest_cntl to pass the value into + * src_node_id: The node ID where the value is coming from + * src_output: The ID of the output on <src_node_id> where the value comes from + * Returns 0 for success + */ +int graph_set_source(struct computation_graph *graph, int dest_node_id, int dest_input, int src_node_id, int src_output); + +/* + * Creates a new node with the given data, and adds it to the graph. + * Returns a negative integer upon failure. + * Otherwise returns a positive integer which is the ID of the new node + */ +int graph_add_node(struct computation_graph *graph, + const char *name, + const struct graph_node_type *type, + void *state); + +/* + * Returns the value at the output of the requested node for the requested output. + * Returns 0 if the given node or output IDs are invalid + */ +double graph_get_output(const struct computation_graph *graph, int node_id, int output_id); + +/* + * Sets a parameter given by param_id on node node_id to the given value + * Returns 0 upon success + */ +int graph_set_param_val(struct computation_graph *graph, int node_id, int param_id, double value); + +/* + * Returns the value of the param at param_id for the given node + * Will return NaN if the given node or parameter IDs are not valid + */ +double graph_get_param_val(const struct computation_graph *graph, int node_id, int param_id); + +/* + * Computes the nodes given by node_id. + * To do so, computes all nodes which are ancestors of each given node_id in topological order, with + * the final computation being node_id itself. + */ +void graph_compute_nodes(struct computation_graph *graph, int* node_ids, int n_nodes); + +/* + * Writes a graphical representation of the given graph to <of> in the DOT language + */ +int export_dot(const struct computation_graph* graph, FILE* of, int print_outputs); + +#endif // __COMPUTATION_GRAPH_H__ diff --git a/quad/computation_graph/src/graph_blocks/node_accumulator.c b/quad/computation_graph/src/graph_blocks/node_accumulator.c new file mode 100644 index 0000000000000000000000000000000000000000..17a36ee92dffc796e9ea592f28ecd1f4737085a1 --- /dev/null +++ b/quad/computation_graph/src/graph_blocks/node_accumulator.c @@ -0,0 +1,39 @@ +#include "node_accumulator.h" +#include <stdlib.h> + +struct accum_state { + double accumulated; +}; + +static void accum_nodes(void *state, const double* params, const double *inputs, double *outputs) { + struct accum_state* my_state = (struct accum_state*)state; + my_state->accumulated += inputs[ACCUM_IN]; + outputs[ACCUMULATED] = my_state->accumulated; +} + +static void reset(void *state) { + ((struct accum_state*)state)->accumulated = 0; +} + + +static const char* const in_names[2] = {"Accumulator in"}; +static const char* const out_names[1] = {"Accumulated"}; +static const char* const param_names[0] = {}; +const struct graph_node_type node_accum_type = { + .input_names = in_names, + .output_names = out_names, + .param_names = param_names, + .n_inputs = 1, + .n_outputs = 1, + .n_params = 0, + .execute = accum_nodes, + .reset = reset +}; + +int graph_add_node_accum(struct computation_graph *graph, const char* name) { + struct accum_state* node_state = malloc(sizeof(struct accum_state)); + if (sizeof(struct accum_state) && !node_state) { + return -1; // malloc failed + } + return graph_add_node(graph, name, &node_accum_type, node_state); +} diff --git a/quad/computation_graph/src/graph_blocks/node_accumulator.h b/quad/computation_graph/src/graph_blocks/node_accumulator.h new file mode 100644 index 0000000000000000000000000000000000000000..2df06c9d1c87473ed2bbb4fc6f8d40aa4b606318 --- /dev/null +++ b/quad/computation_graph/src/graph_blocks/node_accumulator.h @@ -0,0 +1,16 @@ +#ifndef __NODE_ACCUMULATOR_H__ +#define __NODE_ACCUMULATOR_H__ +#include "../computation_graph.h" + +int graph_add_node_accum(struct computation_graph *graph, const char* name); + +extern const struct graph_node_type node_accum_type; + +enum graph_node_accum_inputs { + ACCUM_IN, +}; + +enum graph_node_accum_outputs { + ACCUMULATED +}; +#endif // __NODE_ACCUMULATOR_H__ diff --git a/quad/computation_graph/src/graph_blocks/node_add.c b/quad/computation_graph/src/graph_blocks/node_add.c new file mode 100644 index 0000000000000000000000000000000000000000..eefb22fde9ae9a5cc72d2eb679905f2e70038ac9 --- /dev/null +++ b/quad/computation_graph/src/graph_blocks/node_add.c @@ -0,0 +1,25 @@ +#include "node_add.h" +#include <stdlib.h> + +static void add_nodes(void *state, const double* params, const double *inputs, double *outputs) { + outputs[ADD_SUM] = inputs[ADD_SUMMAND1] + inputs[ADD_SUMMAND2]; +} +static void reset(void *state) {} + +static const char* const in_names[2] = {"Summand 1", "Summand 2"}; +static const char* const out_names[1] = {"Sum"}; +static const char* const param_names[0] = {}; +const struct graph_node_type node_add_type = { + .input_names = in_names, + .output_names = out_names, + .param_names = param_names, + .n_inputs = 2, + .n_outputs = 1, + .n_params = 0, + .execute = add_nodes, + .reset = reset +}; + +int graph_add_node_add(struct computation_graph *graph, const char* name) { + return graph_add_node(graph, name, &node_add_type, NULL); +} diff --git a/quad/computation_graph/src/graph_blocks/node_add.h b/quad/computation_graph/src/graph_blocks/node_add.h new file mode 100644 index 0000000000000000000000000000000000000000..cfd2b71e8b25aecfa38898dbce8427ad97bf6ff9 --- /dev/null +++ b/quad/computation_graph/src/graph_blocks/node_add.h @@ -0,0 +1,17 @@ +#ifndef __NODE_ADD_H__ +#define __NODE_ADD_H__ +#include "../computation_graph.h" + +int graph_add_node_add(struct computation_graph *graph, const char* name); + +extern const struct graph_node_type node_add_type; + +enum graph_node_add_inputs { + ADD_SUMMAND1, + ADD_SUMMAND2, +}; + +enum graph_node_add_outputs { + ADD_SUM +}; +#endif // __NODE_ADD_H__ diff --git a/quad/computation_graph/src/graph_blocks/node_constant.c b/quad/computation_graph/src/graph_blocks/node_constant.c new file mode 100644 index 0000000000000000000000000000000000000000..8ad9f8c5afdb5dc8f4cfacac00f2725ad388a59a --- /dev/null +++ b/quad/computation_graph/src/graph_blocks/node_constant.c @@ -0,0 +1,25 @@ +#include "node_constant.h" +#include <stdlib.h> + +static void output_const(void *state, const double *params, const double *inputs, double *outputs) { + outputs[CONST_VAL] = params[CONST_SET]; +} +static void reset(void *state) {} + +static const char* const in_names[0] = {}; +static const char* const out_names[1] = {"Constant"}; +static const char* const param_names[1] = {"Constant"}; +const struct graph_node_type node_const_type = { + .input_names = in_names, + .output_names = out_names, + .param_names = param_names, + .n_inputs = 0, + .n_outputs = 1, + .n_params = 1, + .execute = output_const, + .reset = reset +}; + +int graph_add_node_const(struct computation_graph *graph, const char* name) { + return graph_add_node(graph, name, &node_const_type, NULL); +} diff --git a/quad/computation_graph/src/graph_blocks/node_constant.h b/quad/computation_graph/src/graph_blocks/node_constant.h new file mode 100644 index 0000000000000000000000000000000000000000..91cd6d055b6bbf64340df4282ac9f9539301f0a5 --- /dev/null +++ b/quad/computation_graph/src/graph_blocks/node_constant.h @@ -0,0 +1,16 @@ +#ifndef __NODE_CONSTANT_H__ +#define __NODE_CONSTANT_H__ +#include "../computation_graph.h" + +int graph_add_node_const(struct computation_graph *graph, const char* name); + +extern const struct graph_node_type node_const_type; + +enum graph_node_const_params { + CONST_SET +}; + +enum graph_node_const_outputs { + CONST_VAL +}; +#endif //__NODE_CONSTANT_H__ diff --git a/quad/computation_graph/src/graph_blocks/node_gain.c b/quad/computation_graph/src/graph_blocks/node_gain.c new file mode 100644 index 0000000000000000000000000000000000000000..35bb3702cf2aa36d2f08f85ed580d12fadbe8fa8 --- /dev/null +++ b/quad/computation_graph/src/graph_blocks/node_gain.c @@ -0,0 +1,25 @@ +#include "node_gain.h" +#include <stdlib.h> + +static void scale_nodes(void *state, const double* params, const double *inputs, double *outputs) { + outputs[GAIN_RESULT] = inputs[GAIN_INPUT] * params[GAIN_GAIN]; +} +static void reset(void *state) {} + +static const char* const in_names[1] = {"Input"}; +static const char* const out_names[1] = {"Amplified"}; +static const char* const param_names[1] = {"Gain"}; +const struct graph_node_type node_gain_type = { + .input_names = in_names, + .output_names = out_names, + .param_names = param_names, + .n_inputs = 1, + .n_outputs = 1, + .n_params = 1, + .execute = scale_nodes, + .reset = reset +}; + +int graph_add_node_gain(struct computation_graph *graph, const char* name) { + return graph_add_node(graph, name, &node_gain_type, NULL); +} diff --git a/quad/computation_graph/src/graph_blocks/node_gain.h b/quad/computation_graph/src/graph_blocks/node_gain.h new file mode 100644 index 0000000000000000000000000000000000000000..b26d3bfe393468c686f8b5b73957e1604594e5d0 --- /dev/null +++ b/quad/computation_graph/src/graph_blocks/node_gain.h @@ -0,0 +1,20 @@ +#ifndef __NODE_GAIN_H__ +#define __NODE_GAIN_H__ +#include "../computation_graph.h" + +int graph_add_node_gain(struct computation_graph *graph, const char* name); + +extern const struct graph_node_type node_gain_type; + +enum graph_node_pow_inputs { + GAIN_INPUT +}; + +enum graph_node_pow_params { + GAIN_GAIN +}; + +enum graph_node_gain_outputs { + GAIN_RESULT +}; +#endif // __NODE_GAIN_H__ diff --git a/quad/computation_graph/src/graph_blocks/node_mult.c b/quad/computation_graph/src/graph_blocks/node_mult.c new file mode 100644 index 0000000000000000000000000000000000000000..2696719fdc83a89ae346d4dc788df693d97b2ddb --- /dev/null +++ b/quad/computation_graph/src/graph_blocks/node_mult.c @@ -0,0 +1,25 @@ +#include "node_mult.h" +#include <stdlib.h> + +static void mult_nodes(void *state, const double* params, const double *inputs, double *outputs) { + outputs[MULT_PRODUCT] = inputs[MULT_MULTIPLICAND1] * inputs[MULT_MULTIPLICAND2]; +} +static void reset(void *state) {} + +static const char* const in_names[2] = {"Multiplicand 1", "Multiplicand 2"}; +static const char* const out_names[1] = {"Product"}; +static const char* const param_names[0] = {}; +const struct graph_node_type node_mult_type = { + .input_names = in_names, + .output_names = out_names, + .param_names = param_names, + .n_inputs = 2, + .n_outputs = 1, + .n_params = 0, + .execute = mult_nodes, + .reset = reset +}; + +int graph_add_node_mult(struct computation_graph *graph, const char* name) { + return graph_add_node(graph, name, &node_mult_type, NULL); +} diff --git a/quad/computation_graph/src/graph_blocks/node_mult.h b/quad/computation_graph/src/graph_blocks/node_mult.h new file mode 100644 index 0000000000000000000000000000000000000000..32d2d873d359ae10bdff6dc857bab6d835c04598 --- /dev/null +++ b/quad/computation_graph/src/graph_blocks/node_mult.h @@ -0,0 +1,18 @@ +#ifndef __NODE_MULT_H__ +#define __NODE_MULT_H__ +#include "../computation_graph.h" + +int graph_add_node_mult(struct computation_graph *graph, const char* name); + +extern const struct graph_node_type node_mult_type; + +enum graph_node_mult_inputs { + MULT_MULTIPLICAND1, + MULT_MULTIPLICAND2, +}; + +enum graph_node_mult_outputs { + MULT_PRODUCT +}; + +#endif // __NODE_MULT_H__ diff --git a/quad/computation_graph/src/graph_blocks/node_pow.c b/quad/computation_graph/src/graph_blocks/node_pow.c new file mode 100644 index 0000000000000000000000000000000000000000..dbbc1d707d7833254e5a96a4dc8ba2b9c954ae4d --- /dev/null +++ b/quad/computation_graph/src/graph_blocks/node_pow.c @@ -0,0 +1,26 @@ +#include "node_pow.h" +#include <stdlib.h> +#include <math.h> + +static void pow_nodes(void *state, const double* params, const double *inputs, double *outputs) { + outputs[POW_RESULT] = pow(inputs[POW_BASE], params[POW_EXP]); +} +static void reset(void *state) {} + +static const char* const in_names[1] = {"Base"}; +static const char* const out_names[1] = {"Result"}; +static const char* const param_names[1] = {"Exponent"}; +const struct graph_node_type node_pow_type = { + .input_names = in_names, + .output_names = out_names, + .param_names = param_names, + .n_inputs = 1, + .n_outputs = 1, + .n_params = 1, + .execute = pow_nodes, + .reset = reset +}; + +int graph_add_node_pow(struct computation_graph *graph, const char* name) { + return graph_add_node(graph, name, &node_pow_type, NULL); +} diff --git a/quad/computation_graph/src/graph_blocks/node_pow.h b/quad/computation_graph/src/graph_blocks/node_pow.h new file mode 100644 index 0000000000000000000000000000000000000000..11bdd9173c7522683e3b8af7fec809632b856c19 --- /dev/null +++ b/quad/computation_graph/src/graph_blocks/node_pow.h @@ -0,0 +1,20 @@ +#ifndef __NODE_POW_H__ +#define __NODE_POW_H__ +#include "../computation_graph.h" + +int graph_add_node_pow(struct computation_graph *graph, const char* name); + +extern const struct graph_node_type node_pow_type; + +enum graph_node_pow_inputs { + POW_BASE +}; + +enum graph_node_pow_params { + POW_EXP +}; + +enum graph_node_pow_outputs { + POW_RESULT +}; +#endif // __NODE_POW_H__ diff --git a/quad/computation_graph/src/main.c b/quad/computation_graph/src/main.c new file mode 100644 index 0000000000000000000000000000000000000000..b7fb2d3302228793a67465812e155e37e2f3b1f8 --- /dev/null +++ b/quad/computation_graph/src/main.c @@ -0,0 +1,42 @@ +#include <stdio.h> +#include "computation_graph.h" +#include "graph_blocks/node_add.h" +#include "graph_blocks/node_mult.h" +#include "graph_blocks/node_constant.h" +#include "graph_blocks/node_gain.h" +#include "tests.h" + +int main() { +// struct computation_graph *graph = create_graph(); +// +// int const1 = graph_add_node_const(graph, "Const 2"); +// graph_set_param_val(graph, const1, CONST_SET, 2); +// int const2 = graph_add_node_const(graph, "Const 1"); +// graph_set_param_val(graph, const2, CONST_SET, 3); +// +// int add1_id = graph_add_node_add(graph, "Add"); +// graph_set_source(graph, add1_id, ADD_SUMMAND1, const1, CONST_VAL); +// graph_set_source(graph, add1_id, ADD_SUMMAND2, const2, CONST_VAL); +// +// int gain1_id = graph_add_node_gain(graph, "Gain"); +// graph_set_param_val(graph, gain1_id, GAIN_GAIN, 3); +// graph_set_source(graph, gain1_id, GAIN_INPUT, add1_id, ADD_SUM); +// +// int mult1_id = graph_add_node_mult(graph, "Mult"); +// graph_set_source(graph, mult1_id, MULT_MULTIPLICAND2, gain1_id, GAIN_RESULT); +// graph_set_source(graph, mult1_id, MULT_MULTIPLICAND1, const1, CONST_VAL); +// +// graph_compute_node(graph, mult1_id); + +// FILE* dot_fp; +// dot_fp = fopen("..\\comp_graph.dot", "w"); +// export_dot(graph, dot_fp); +// fclose(dot_fp); +// printf("Sum is %f\n", graph_get_output(graph, mult1_id, GAIN_RESULT)); + + int success = graph_run_tests(); + printf("Success: %s\n", success == 0 ? "Yes" : "No"); + fflush(stdout); + + return 0; +} diff --git a/quad/computation_graph/src/tests.c b/quad/computation_graph/src/tests.c new file mode 100644 index 0000000000000000000000000000000000000000..2f120a2693f4b2ee8116d192b4494299f93f44a8 --- /dev/null +++ b/quad/computation_graph/src/tests.c @@ -0,0 +1,151 @@ +// +// Created by dawehr on 2/9/2017. +// + +#include "tests.h" +#define GRAPH_TEST_EPS 0.00001 + +static int nequal(double val1, double val2) { + if (fabs(val1 - val2) < GRAPH_TEST_EPS) { + return 0; + } + return -1; +} + +int graph_run_tests() { + int success = 0; + success |= graph_test_one_add(); + success |= graph_test_circular_runs(); + success |= graph_test_circular_resets(); + success |= graph_test_accumulator(); + success |= graph_test_single_run(); + success |= graph_test_reset_rules(); + success |= graph_test_self_loop(); + return success; +} + +int graph_test_one_add() { + struct computation_graph *graph = create_graph(); + int block = graph_add_node_add(graph, "Add"); + int cblock3 = graph_add_node_const(graph, "3"); + graph_set_param_val(graph, cblock3, CONST_SET, 3); + int cblock4 = graph_add_node_const(graph, "4"); + graph_set_param_val(graph, cblock4, CONST_SET, 4); + graph_set_source(graph, block, ADD_SUMMAND1, cblock3, CONST_VAL); + graph_set_source(graph, block, ADD_SUMMAND2, cblock4, CONST_VAL); + int to_compute_for[1] = {block}; + graph_compute_nodes(graph, to_compute_for, 1); + double result = graph_get_output(graph, block, ADD_SUM); + return nequal(result, 7); +} + + +int graph_test_circular_runs() { + struct computation_graph *graph = create_graph(); + int gain1 = graph_add_node_gain(graph, "gain1"); + int gain2 = graph_add_node_gain(graph, "gain2"); + graph_set_source(graph, gain2, GAIN_INPUT, gain1, GAIN_RESULT); + graph_set_source(graph, gain1, GAIN_INPUT, gain2, GAIN_RESULT); + int to_compute_for[1] = {gain2}; + graph_compute_nodes(graph, to_compute_for, 1); + // If no infinite loop, then success. Value is undefined for circular graphs + return 0; +} + +int graph_test_circular_resets() { + struct computation_graph *graph = create_graph(); + int acum1 = graph_add_node_accum(graph, "accumulator1"); + int acum2 = graph_add_node_accum(graph, "accumulator2"); + graph_set_source(graph, acum2, ACCUM_IN, acum1, ACCUMULATED); + graph_set_source(graph, acum1, ACCUM_IN, acum2, ACCUMULATED); + return 0; // Passes if no infinite loop +} + +// Tests the accumulator block, thereby testing reset and state changes +int graph_test_accumulator() { + struct computation_graph *graph = create_graph(); + int cblock = graph_add_node_const(graph, "const"); + int acum_b = graph_add_node_accum(graph, "accumulator"); + graph_set_source(graph, acum_b, ACCUM_IN, cblock, CONST_VAL); + + int to_compute_for[1] = {acum_b}; + graph_set_param_val(graph, cblock, CONST_SET, 3); + graph_compute_nodes(graph, to_compute_for, 1); + graph_set_param_val(graph, cblock, CONST_SET, 8); + graph_compute_nodes(graph, to_compute_for, 1); + graph_set_param_val(graph, cblock, CONST_SET, -2); + graph_compute_nodes(graph, to_compute_for, 1); + + double result = graph_get_output(graph, acum_b, ACCUMULATED); + if (nequal(result, 9)) { + printf("graph_test_accumulator failed on step 1, equals %f\n", result); + return -1; + } + + // Test reset on source set + int gain_b = graph_add_node_gain(graph, "Gain"); + graph_set_param_val(graph, gain_b, GAIN_GAIN, 1); + graph_set_source(graph, gain_b, GAIN_INPUT, acum_b, ACCUMULATED); + to_compute_for[0] = gain_b; + graph_compute_nodes(graph, to_compute_for, 1); + result = graph_get_output(graph, gain_b, GAIN_RESULT); + if (nequal(result, -2)) { + printf("graph_test_accumulator failed on step 2\n"); + return -2; + } + return 0; +} + +// Tests that a block will only execute once per compute, +// even if its output is connected to multiple inputs +int graph_test_single_run() { + struct computation_graph *graph = create_graph(); + int acum_b = graph_add_node_accum(graph, "accumulator"); + int add_block = graph_add_node_add(graph, "Add"); + int cblock = graph_add_node_const(graph, "const"); + graph_set_param_val(graph, cblock, CONST_SET, 2); + + + graph_set_source(graph, acum_b, ACCUM_IN, cblock, CONST_VAL); + graph_set_source(graph, add_block, ADD_SUMMAND1, acum_b, ACCUMULATED); + graph_set_source(graph, add_block, ADD_SUMMAND2, acum_b, ACCUMULATED); + + int to_compute_for[1] = {add_block}; + graph_compute_nodes(graph, to_compute_for, 1); + double result = graph_get_output(graph, add_block, ADD_SUM); + return nequal(result, 4); +} + +// Tests that upon connection of a second child, a block will not reset +int graph_test_reset_rules() { + struct computation_graph *graph = create_graph(); + int cblock = graph_add_node_const(graph, "5"); + graph_set_param_val(graph, cblock, CONST_SET, 5); + int acum_b = graph_add_node_accum(graph, "accumulator"); + int gain1 = graph_add_node_gain(graph, "gain1"); + graph_set_param_val(graph, gain1, GAIN_GAIN, 1); + + graph_set_source(graph, gain1, GAIN_INPUT, acum_b, ACCUMULATED); + graph_set_source(graph, acum_b, ACCUM_IN, cblock, CONST_VAL); + int to_compute_for[1] = {gain1}; + graph_compute_nodes(graph, to_compute_for, 1); + // state of acum_b is now 5 + + int gain2 = graph_add_node_gain(graph, "gain2"); + graph_set_param_val(graph, gain2, GAIN_GAIN, 1); + // Connect gain 2, and accumulator should not get reset + graph_set_source(graph, gain2, GAIN_INPUT, acum_b, ACCUMULATED); + to_compute_for[0] = gain2; + graph_compute_nodes(graph, to_compute_for, 1); + double result = graph_get_output(graph, gain2, GAIN_RESULT); + return nequal(result, 10); +} + +int graph_test_self_loop() { + struct computation_graph *graph = create_graph(); + int gain1 = graph_add_node_gain(graph, "gain1"); + graph_set_source(graph, gain1, GAIN_INPUT, gain1, GAIN_RESULT); + int to_compute_for[1] = {gain1}; + graph_compute_nodes(graph, to_compute_for, 1); + return 0; +} diff --git a/quad/computation_graph/src/tests.h b/quad/computation_graph/src/tests.h new file mode 100644 index 0000000000000000000000000000000000000000..b30b7d806b88f3811b74f264742d4094cd4387c8 --- /dev/null +++ b/quad/computation_graph/src/tests.h @@ -0,0 +1,32 @@ +// +// Created by dawehr on 2/9/2017. +// + +#ifndef COMPUTATION_GRAPH_TESTS_H +#define COMPUTATION_GRAPH_TESTS_H + +#include "computation_graph.h" +#include "graph_blocks/node_add.h" +#include "graph_blocks/node_mult.h" +#include "graph_blocks/node_constant.h" +#include "graph_blocks/node_gain.h" +#include "graph_blocks/node_accumulator.h" +#include <math.h> + +int graph_run_tests(); + +int graph_test_one_add(); + +int graph_test_circular_runs(); + +int graph_test_circular_resets(); + +int graph_test_self_loop(); + +int graph_test_accumulator(); + +int graph_test_single_run(); + +int graph_test_reset_rules(); + +#endif //COMPUTATION_GRAPH_TESTS_H diff --git a/quad/computation_graph/test/Makefile b/quad/computation_graph/test/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..4fb6cf11603044352c832c27ce2b2a21edc58147 --- /dev/null +++ b/quad/computation_graph/test/Makefile @@ -0,0 +1,12 @@ +# QUAD_ROOT is obtained from environment +SRC = $(QUAD_ROOT)/computation_graph/src/graph_blocks/*.c $(QUAD_ROOT)/computation_graph/src/computation_graph.c +INC = $(QUAD_ROOT)/computation_graph/src +BLOCKS_INC = $(QUAD_ROOT)/computation_graph/src/graph_blocks +LIB = $(QUAD_ROOT)/lib/test + +test_computation_graph: test_computation_graph.c $(SRC) + gcc -o test_computation_graph -I. -I$(INC) -I$(BLOCKS_INC) -I$(LIB) $(LIB)/test.o test_computation_graph.c $(SRC) -lm + +.PHONY: clean +clean: + rm test_computation_graph diff --git a/quad/computation_graph/test/test_computation_graph.c b/quad/computation_graph/test/test_computation_graph.c new file mode 100644 index 0000000000000000000000000000000000000000..9d7c96bbc4c42349978b13d62c7d7036d20b516c --- /dev/null +++ b/quad/computation_graph/test/test_computation_graph.c @@ -0,0 +1,233 @@ +#include "test.h" + + +#include "computation_graph.h" +#include "graph_blocks/node_add.h" +#include "graph_blocks/node_mult.h" +#include "graph_blocks/node_constant.h" +#include "graph_blocks/node_gain.h" +#include "graph_blocks/node_accumulator.h" + +#define GRAPH_TEST_EPS 0.00001 + +static int nequal(double val1, double val2) { + if (fabs(val1 - val2) < GRAPH_TEST_EPS) { + return 0; + } + return -1; +} + +int graph_test_one_add() { + struct computation_graph *graph = create_graph(); + int block = graph_add_node_add(graph, "Add"); + int cblock3 = graph_add_node_const(graph, "3"); + graph_set_param_val(graph, cblock3, CONST_SET, 3); + int cblock4 = graph_add_node_const(graph, "4"); + graph_set_param_val(graph, cblock4, CONST_SET, 4); + graph_set_source(graph, block, ADD_SUMMAND1, cblock3, CONST_VAL); + graph_set_source(graph, block, ADD_SUMMAND2, cblock4, CONST_VAL); + int to_compute_for[1] = {block}; + graph_compute_nodes(graph, to_compute_for, 1); + double result = graph_get_output(graph, block, ADD_SUM); + return nequal(result, 7); +} + + +int graph_test_circular_runs() { + struct computation_graph *graph = create_graph(); + int gain1 = graph_add_node_gain(graph, "gain1"); + int gain2 = graph_add_node_gain(graph, "gain2"); + graph_set_source(graph, gain2, GAIN_INPUT, gain1, GAIN_RESULT); + graph_set_source(graph, gain1, GAIN_INPUT, gain2, GAIN_RESULT); + int to_compute_for[1] = {gain2}; + graph_compute_nodes(graph, to_compute_for, 1); + // If no infinite loop, then success. Value is undefined for circular graphs + return 0; +} + +int graph_test_circular_resets() { + struct computation_graph *graph = create_graph(); + int acum1 = graph_add_node_accum(graph, "accumulator1"); + int acum2 = graph_add_node_accum(graph, "accumulator2"); + graph_set_source(graph, acum2, ACCUM_IN, acum1, ACCUMULATED); + graph_set_source(graph, acum1, ACCUM_IN, acum2, ACCUMULATED); + return 0; // Passes if no infinite loop +} + +// Tests the accumulator block, thereby testing reset and state changes +int graph_test_accumulator() { + struct computation_graph *graph = create_graph(); + int cblock = graph_add_node_const(graph, "const"); + int acum_b = graph_add_node_accum(graph, "accumulator"); + graph_set_source(graph, acum_b, ACCUM_IN, cblock, CONST_VAL); + + int to_compute_for[1] = {acum_b}; + graph_set_param_val(graph, cblock, CONST_SET, 3); + graph_compute_nodes(graph, to_compute_for, 1); + graph_set_param_val(graph, cblock, CONST_SET, 8); + graph_compute_nodes(graph, to_compute_for, 1); + graph_set_param_val(graph, cblock, CONST_SET, -2); + graph_compute_nodes(graph, to_compute_for, 1); + + double result = graph_get_output(graph, acum_b, ACCUMULATED); + if (nequal(result, 9)) { + printf("graph_test_accumulator failed on step 1, equals %f\n", result); + return -1; + } + + // Test reset on source set + int gain_b = graph_add_node_gain(graph, "Gain"); + graph_set_param_val(graph, gain_b, GAIN_GAIN, 1); + graph_set_source(graph, gain_b, GAIN_INPUT, acum_b, ACCUMULATED); + to_compute_for[0] = gain_b; + graph_compute_nodes(graph, to_compute_for, 1); + result = graph_get_output(graph, gain_b, GAIN_RESULT); + if (nequal(result, -2)) { + printf("graph_test_accumulator failed on step 2\n"); + return -2; + } + return 0; +} + +// Tests that a block will only execute once per compute, +// even if its output is connected to multiple inputs +int graph_test_single_run() { + struct computation_graph *graph = create_graph(); + int acum_b = graph_add_node_accum(graph, "accumulator"); + int add_block = graph_add_node_add(graph, "Add"); + int cblock = graph_add_node_const(graph, "const"); + graph_set_param_val(graph, cblock, CONST_SET, 2); + + + graph_set_source(graph, acum_b, ACCUM_IN, cblock, CONST_VAL); + graph_set_source(graph, add_block, ADD_SUMMAND1, acum_b, ACCUMULATED); + graph_set_source(graph, add_block, ADD_SUMMAND2, acum_b, ACCUMULATED); + + int to_compute_for[1] = {add_block}; + graph_compute_nodes(graph, to_compute_for, 1); + double result = graph_get_output(graph, add_block, ADD_SUM); + return nequal(result, 4); +} + +// Tests that upon connection of a second child, a block will not reset +int graph_test_reset_rules() { + struct computation_graph *graph = create_graph(); + int cblock = graph_add_node_const(graph, "5"); + graph_set_param_val(graph, cblock, CONST_SET, 5); + int acum_b = graph_add_node_accum(graph, "accumulator"); + int gain1 = graph_add_node_gain(graph, "gain1"); + graph_set_param_val(graph, gain1, GAIN_GAIN, 1); + + graph_set_source(graph, gain1, GAIN_INPUT, acum_b, ACCUMULATED); + graph_set_source(graph, acum_b, ACCUM_IN, cblock, CONST_VAL); + int to_compute_for[1] = {gain1}; + graph_compute_nodes(graph, to_compute_for, 1); + // state of acum_b is now 5 + + int gain2 = graph_add_node_gain(graph, "gain2"); + graph_set_param_val(graph, gain2, GAIN_GAIN, 1); + // Connect gain 2, and accumulator should not get reset + graph_set_source(graph, gain2, GAIN_INPUT, acum_b, ACCUMULATED); + to_compute_for[0] = gain2; + graph_compute_nodes(graph, to_compute_for, 1); + double result = graph_get_output(graph, gain2, GAIN_RESULT); + // Equals 5 and not 10 because the inputs to the accumulator did not change, + // so it didn't run again' + return nequal(result, 5); +} + +int graph_test_self_loop() { + struct computation_graph *graph = create_graph(); + int gain1 = graph_add_node_gain(graph, "gain1"); + graph_set_source(graph, gain1, GAIN_INPUT, gain1, GAIN_RESULT); + int to_compute_for[1] = {gain1}; + graph_compute_nodes(graph, to_compute_for, 1); + return 0; +} + +int graph_test_update_rules() { + struct computation_graph *graph = create_graph(); + int cblock = graph_add_node_const(graph, "const"); + int acum_b = graph_add_node_accum(graph, "accumulator"); + graph_set_source(graph, acum_b, ACCUM_IN, cblock, CONST_VAL); + + graph_set_param_val(graph, cblock, CONST_SET, 3); + int to_compute_for[1] = {acum_b}; + graph_compute_nodes(graph, to_compute_for, 1); + graph_compute_nodes(graph, to_compute_for, 1); + + double result = graph_get_output(graph, acum_b, ACCUMULATED); + return nequal(result, 3); +} + +/* +C1 --->| accum_b1 --->| + | Add ---> +C2 --->| accum_b2 --->| +*/ +int graph_test_update_propagation() { + struct computation_graph *graph = create_graph(); + int cblock1 = graph_add_node_const(graph, "const1"); + int cblock2 = graph_add_node_const(graph, "const2"); + int accum_b1 = graph_add_node_accum(graph, "accumulator1"); + int accum_b2 = graph_add_node_accum(graph, "accumulator2"); + int add_b = graph_add_node_add(graph, "add"); + graph_set_source(graph, accum_b1, ACCUM_IN, cblock1, CONST_VAL); + graph_set_source(graph, accum_b2, ACCUM_IN, cblock2, CONST_VAL); + graph_set_source(graph, add_b, ADD_SUMMAND1, accum_b1, ACCUMULATED); + graph_set_source(graph, add_b, ADD_SUMMAND2, accum_b2, ACCUMULATED); + graph_set_param_val(graph, cblock1, CONST_SET, 5); + graph_set_param_val(graph, cblock2, CONST_SET, 2); + int to_compute_for[] = {add_b}; + graph_compute_nodes(graph, to_compute_for, 1); + double result1 = graph_get_output(graph, add_b, ADD_SUM); + graph_set_param_val(graph, cblock1, CONST_SET, 1); + graph_compute_nodes(graph, to_compute_for, 1); + double result2 = graph_get_output(graph, add_b, ADD_SUM); + + return nequal(result1, 7) || nequal(result2, 8); +} + +/* +Tests for a really subtle edge case +If a node is disconnected from the computation path, then it will not get computed, even if it is "updated" +After computation, nodes get their updated flag cleared. It used to clear all nodes, even if they weren't processed (now it only clears if they were processed) +This caused problems, because if a node had its output to two things, then it wouldn't get marked updated by the reset propagation. +Since it didn't get marked as "updated" when it got connected to the computation path, and it had its original "updated" flag cleared, +the node would never get set. +*/ +int graph_test_update_disconnected() { + printf("\n\n---------\n"); + struct computation_graph *graph = create_graph(); + int d_block = graph_add_node_const(graph, "const1"); + int gain_block = graph_add_node_gain(graph, "gain"); + int gain2_block = graph_add_node_gain(graph, "gain2"); + int const_b = graph_add_node_const(graph, "const2"); + graph_set_source(graph, gain_block, GAIN_INPUT, const_b, CONST_VAL); + graph_set_source(graph, gain2_block, GAIN_INPUT, d_block, CONST_VAL); // We need this so d_block doesn't get updated + graph_set_param_val(graph, gain_block, GAIN_GAIN, 2); + graph_set_param_val(graph, d_block, CONST_SET, 1.2345); // Make sure this gets set even if disconnected + + int to_compute_for[] = {gain_block}; + graph_compute_nodes(graph, to_compute_for, 1); + graph_set_source(graph, gain_block, GAIN_INPUT, d_block, CONST_VAL); + graph_compute_nodes(graph, to_compute_for, 1); + + double set_val = graph_get_output(graph, gain_block, GAIN_RESULT); + return nequal(set_val, 2*1.2345); +} + +int main() { + int success = 0; + test(graph_test_one_add, "Test adding 2 numbers"); + test(graph_test_circular_runs, "Test computing cycles"); + test(graph_test_circular_resets, "Test resetting cycles"); + test(graph_test_accumulator, "Test accumulator (state)"); + test(graph_test_single_run, "Test that blocks only get executed once"); + test(graph_test_reset_rules, "Tests that already connected blocks don't get reset"); + test(graph_test_self_loop, "Tests that a self-loop computation terminates"); + test(graph_test_update_rules, "Tests that nodes only update when their inputs change"); + test(graph_test_update_propagation, "Tests that updates propagate only to their children"); + test(graph_test_update_disconnected, "Tests that nodes get executed when updated, even if disconnected"); + test_summary(); +} diff --git a/quad/scripts/tcp_stress_tests.py b/quad/scripts/tcp_stress_tests.py new file mode 100644 index 0000000000000000000000000000000000000000..7619a75147ea695fee2b444fc53ba0744c169313 --- /dev/null +++ b/quad/scripts/tcp_stress_tests.py @@ -0,0 +1,85 @@ +import socket +import time +import uart_stress_tests + +TCP_IP = "192.168.1.1" +TCP_PORT = 8080 + +send_delay = 0.001 +TEST_ITERS = 100000 +REPORT_STEP = int(TEST_ITERS / 10) +payload_size = 28 + +received = [] +def read_n(n_to_read): + global received + while len(received) < n_to_read: + try: + just_received = sock.recv(1024) + if len(just_received) == 0: + print("Socket broken") + quit() + received.extend(just_received) + except: + pass + to_ret = bytes(received[0:n_to_read]) + received = received[n_to_read:] + return to_ret + +def read_packet(raw=False): + header = read_n(7) + length = int.from_bytes(header[5:7], byteorder='little') + data = read_n(length) + checksum = read_n(1) + if raw: + return header + data + checksum + else: + return data + +def get_quad_status(): + print("Getting quad status...") + query_msg = uart_stress_tests.create_msg(2, 0, b'') + sock.send(query_msg) + resp = read_packet() + return uart_stress_tests.parse_query_response(resp) + print("Got quad status") + + +if __name__ == '__main__': + # connect + print("Connecting...") + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(2) + try: + sock.connect((TCP_IP, TCP_PORT)) + sock.setblocking(True) + except: + print("Failed to connect") + quit() + print("connected") + + initial_status = get_quad_status() + + message = uart_stress_tests.create_test_packet(payload_size) + overall_start_time = time.perf_counter() + for i in range(TEST_ITERS): + if i % REPORT_STEP == 0: + print("Sent {} messages".format(i)) + start_time = time.perf_counter() + sock.send(message) + # Busy waiting + # to_sleep_for = send_delay - (time.time() - start_time) + # if to_sleep_for > 1e-6: + # time.sleep(to_sleep_for) + while (time.perf_counter() - start_time <= send_delay): + pass + # Reporting + avg_time = (time.perf_counter() - overall_start_time) / TEST_ITERS + print("Average send time was {}".format(avg_time)) + + after_status = get_quad_status() + diff_messages = after_status[0] - initial_status[0] + diff_payload = after_status[1] - initial_status[1] + print("Sent {} messages, total payload of {}".format(TEST_ITERS, TEST_ITERS*payload_size)) + print("Recv {} messages, total payload of {}".format(diff_messages, diff_payload)) + print("Lost {} messages, {} bytes".format(TEST_ITERS - diff_messages, (TEST_ITERS*payload_size) - diff_payload)) diff --git a/quad/scripts/stress_tests.py b/quad/scripts/uart_stress_tests.py similarity index 97% rename from quad/scripts/stress_tests.py rename to quad/scripts/uart_stress_tests.py index b038c8af18a2a29245afc4773ef226b2a67dd7b9..ecbb27b246b7732cd514aa2299f7eb575d8b00ce 100755 --- a/quad/scripts/stress_tests.py +++ b/quad/scripts/uart_stress_tests.py @@ -14,7 +14,7 @@ def create_msg(msg_type, msg_id, data): msg += msg_id.to_bytes(2, 'little') msg += len(data).to_bytes(2, 'little') msg += data - + checksum = 0 for b in msg: checksum ^= b @@ -35,6 +35,13 @@ def read_packet(ser, raw=False): else: return data +def parse_query_response(data): + received_str = data[:-1].decode('ascii') + if len(received_str) == 0: + print("Timed out") + return (-1,-1) + return tuple(map(int, received_str.split(','))) + def query_received(ser): # Send request query_msg = create_msg(2, 0, b'') @@ -42,11 +49,7 @@ def query_received(ser): ser.flush() sleep(0.1) resp = read_packet(ser) - received_str = resp[:-1].decode('ascii') - if len(received_str) == 0: - print("Timed out") - return (-1,-1) - return tuple(map(int, received_str.split(','))) + return parse_query_response(resp) def check_test(ser, n_sent, size_sent, old_status): new_n, new_size = query_received(ser) @@ -68,7 +71,7 @@ def test_checksum(ser): computed_checksum = 0 for i in range(len(raw_data) - 1): computed_checksum ^= raw_data[i] - + valid = computed_checksum == given_checksum ret_status = query_received(ser) if not valid: @@ -124,27 +127,26 @@ def test_get_set(ser): resp_cntl = resp[0] resp_cnst = resp[1] resp_val = struct.unpack('f', resp[2:6])[0] - + if resp_cntl != cntl_id or resp_cnst != const_id or abs(resp_val - to_set) > 1e-5: - print("Failed get/set test. Expected controller " + + print("Failed get/set test. Expected controller " + str(cntl_id) + ", constant " + str(const_id) + ", value " + str(to_set)) print(" Received " + str(resp_cntl) + ", constant " + str(resp_cnst) + ", value " + str(resp_val)) passed = False ret_status = query_received(ser) return passed, ret_status - + if __name__ == '__main__': with serial.Serial('/dev/ttyUSB0', 921600, timeout=5) as ser: ser.reset_input_buffer() while (ser.in_waiting != 0): ser.read() status = query_received(ser) - + passed, status = test_partial_packet(ser, status) passed, status = test_blast(ser, status) passed, status = test_blast(ser, status, 150, 80) passed, status = test_bad_checksum(ser, status) passed, status = test_checksum(ser) passed, status = test_get_set(ser) - diff --git a/quad/sw/modular_quad_pid/.cproject b/quad/sw/modular_quad_pid/.cproject index c203f31db275e586aceec2167d98420327afe1ed..2a974bb61e6abb6f71e0b057d0beb3c3c2a5b789 100644 --- a/quad/sw/modular_quad_pid/.cproject +++ b/quad/sw/modular_quad_pid/.cproject @@ -71,7 +71,7 @@ </toolChain> </folderInfo> <sourceEntries> - <entry excluding="test" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/> + <entry excluding="gen_diagram|test" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/> </sourceEntries> </configuration> </storageModule> @@ -147,7 +147,7 @@ </toolChain> </folderInfo> <sourceEntries> - <entry excluding="test" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/> + <entry excluding="gen_diagram|test" flags="VALUE_WORKSPACE_PATH|RESOLVED" kind="sourcePath" name=""/> </sourceEntries> </configuration> </storageModule> diff --git a/quad/sw/modular_quad_pid/gen_diagram/.gitignore b/quad/sw/modular_quad_pid/gen_diagram/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..3309c19da797a4bb0267176e58ce348e67656baf --- /dev/null +++ b/quad/sw/modular_quad_pid/gen_diagram/.gitignore @@ -0,0 +1 @@ +gen_diagram diff --git a/quad/sw/modular_quad_pid/gen_diagram/Makefile b/quad/sw/modular_quad_pid/gen_diagram/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..9c7cb2a11c2cb0bc77ee5b7b6eb0ce3bf18c3048 --- /dev/null +++ b/quad/sw/modular_quad_pid/gen_diagram/Makefile @@ -0,0 +1,9 @@ + +QUAD_BLOCKS = ../src/graph_blocks + +gen_diagram: generate.c ../src/control_algorithm.c ../src/computation_graph.c + gcc -o gen_diagram -I. -I../src/ generate.c ../src/computation_graph.c ../src/control_algorithm.c ../src/graph_blocks/*.c -Dread_flap=freadflap + +.PHONY: clean +clean: + rm gen_diagram diff --git a/quad/sw/modular_quad_pid/gen_diagram/README.md b/quad/sw/modular_quad_pid/gen_diagram/README.md new file mode 100644 index 0000000000000000000000000000000000000000..87b45825befd9e43f373eab8b973ba098e9cd147 --- /dev/null +++ b/quad/sw/modular_quad_pid/gen_diagram/README.md @@ -0,0 +1,9 @@ +The gen_diagram project can create an image of the controller network. Below outlines the process of how to use it. + +Within the `quad/sw/modular_quad_pid` directory: + +1. Within the `gen_diagram` folder, run `make` to build the new code. +2. Now execute the program with `./gen_diagram` +3. If you have graphviz installed, you can run `dot -Tpng network.dot -o network.png` to generate a PNG file of the graph. + +Enjoy your new diagram! diff --git a/quad/sw/modular_quad_pid/gen_diagram/gen_diagram b/quad/sw/modular_quad_pid/gen_diagram/gen_diagram new file mode 100755 index 0000000000000000000000000000000000000000..3babc8f1ab7b00d2bdd2ed28c38a08c8976f1fca Binary files /dev/null and b/quad/sw/modular_quad_pid/gen_diagram/gen_diagram differ diff --git a/quad/sw/modular_quad_pid/gen_diagram/generate.c b/quad/sw/modular_quad_pid/gen_diagram/generate.c new file mode 100644 index 0000000000000000000000000000000000000000..d88d8598b3963b768966c16a46231e7587aa130d --- /dev/null +++ b/quad/sw/modular_quad_pid/gen_diagram/generate.c @@ -0,0 +1,23 @@ +#include <stdio.h> + +#include "computation_graph.h" +#include "graph_blocks/node_pid.h" +#include "graph_blocks/node_bounds.h" +#include "graph_blocks/node_constant.h" +#include "graph_blocks/node_mixer.h" +#include "PID.h" +#include "control_algorithm.h" + +#define ROLL_PITCH_MAX_ANGLE 0.3490 // 20 degrees +parameter_t ps; + +//int control_algorithm_init(parameter_t * ps); +int freadflap(int i) {return i;} + +int main() { + control_algorithm_init(&ps); + FILE* dot_fp; + dot_fp = fopen("network.dot", "w"); + export_dot(ps.graph, dot_fp, 0); + fclose(dot_fp); +} diff --git a/quad/sw/modular_quad_pid/gen_diagram/local_PID.h b/quad/sw/modular_quad_pid/gen_diagram/local_PID.h new file mode 100644 index 0000000000000000000000000000000000000000..d0ff70e210bcfb8833d123808049291174bf1d01 --- /dev/null +++ b/quad/sw/modular_quad_pid/gen_diagram/local_PID.h @@ -0,0 +1,83 @@ +/* + * PID.h + * + * Created on: Nov 10, 2014 + * Author: ucart + */ + +#ifndef PID_H_ +#define PID_H_ + +// Yaw constants + +// when using units of degrees +//#define YAW_ANGULAR_VELOCITY_KP 40.0f +//#define YAW_ANGULAR_VELOCITY_KI 0.0f +//#define YAW_ANGULAR_VELOCITY_KD 0.0f +//#define YAW_ANGLE_KP 2.6f +//#define YAW_ANGLE_KI 0.0f +//#define YAW_ANGLE_KD 0.0f + +// when using units of radians +#define YAW_ANGULAR_VELOCITY_KP 190.0f * 2292.0f//200.0f * 2292.0f +#define YAW_ANGULAR_VELOCITY_KI 0.0f +#define YAW_ANGULAR_VELOCITY_KD 0.0f +#define YAW_ANGLE_KP 2.6f +#define YAW_ANGLE_KI 0.0f +#define YAW_ANGLE_KD 0.0f + +// Roll constants +//#define ROLL_ANGULAR_VELOCITY_KP 0.95f +//#define ROLL_ANGULAR_VELOCITY_KI 0.0f +//#define ROLL_ANGULAR_VELOCITY_KD 0.13f//0.4f//0.7f +//#define ROLL_ANGLE_KP 17.0f //9.0f +//#define ROLL_ANGLE_KI 0.0f +//#define ROLL_ANGLE_KD 0.3f // 0.2f +//#define YPOS_KP 0.0f +//#define YPOS_KI 0.0f +//#define YPOS_KD 0.0f + +// when using units of radians +#define ROLL_ANGULAR_VELOCITY_KP 100.0f*46.0f//102.0f*46.0f//9384.0f//204.0f * 46.0f +#define ROLL_ANGULAR_VELOCITY_KI 0.0f +#define ROLL_ANGULAR_VELOCITY_KD 100.f*5.5f//102.0f*6.8f//1387.2//204.0f * 6.8f +#define ROLL_ANGLE_KP 15.0f +#define ROLL_ANGLE_KI 0.0f +#define ROLL_ANGLE_KD 0.2f +#define YPOS_KP 0.015f +#define YPOS_KI 0.005f +#define YPOS_KD 0.03f + + +//Pitch constants + +// when using units of degrees +//#define PITCH_ANGULAR_VELOCITY_KP 0.95f +//#define PITCH_ANGULAR_VELOCITY_KI 0.0f +//#define PITCH_ANGULAR_VELOCITY_KD 0.13f//0.35f//0.7f +//#define PITCH_ANGLE_KP 17.0f // 7.2f +//#define PITCH_ANGLE_KI 0.0f +//#define PITCH_ANGLE_KD 0.3f //0.3f +//#define XPOS_KP 40.0f +//#define XPOS_KI 0.0f +//#define XPOS_KD 10.0f//0.015f + +// when using units of radians +#define PITCH_ANGULAR_VELOCITY_KP 100.0f*46.0f//101.0f*46.0f//9292.0f//202.0f * 46.0f +#define PITCH_ANGULAR_VELOCITY_KI 0.0f +#define PITCH_ANGULAR_VELOCITY_KD 100.0f*5.5f//101.0f*6.8f//1373.6//202.0f * 6.8f +#define PITCH_ANGLE_KP 15.0f +#define PITCH_ANGLE_KI 0.0f +#define PITCH_ANGLE_KD 0.2f +#define XPOS_KP -0.015f +#define XPOS_KI -0.005f +#define XPOS_KD -0.03f + + +//Throttle constants +#define ALT_ZPOS_KP 9804.0f +#define ALT_ZPOS_KI 817.0f +#define ALT_ZPOS_KD 7353.0f + + +#endif /* PID_H_ */ diff --git a/quad/sw/modular_quad_pid/gen_diagram/network.dot b/quad/sw/modular_quad_pid/gen_diagram/network.dot new file mode 100644 index 0000000000000000000000000000000000000000..516c4a31b125323ad1be1cd32b7e128459789e3f --- /dev/null +++ b/quad/sw/modular_quad_pid/gen_diagram/network.dot @@ -0,0 +1,111 @@ +digraph G { +rankdir="LR" +"Roll PID"[shape=record +label="<f0>Roll PID |<f1> --\>Cur point |<f2> --\>Setpoint |<f3> --\>dt |<f4> [Kp=15.000] |<f5> [Ki=0.000] |<f6> [Kd=0.200]"] +"Roll" -> "Roll PID":f1 [label="Constant"] +"RC Roll" -> "Roll PID":f2 [label="Constant"] +"Ts_angle" -> "Roll PID":f3 [label="Constant"] +"Pitch PID"[shape=record +label="<f0>Pitch PID |<f1> --\>Cur point |<f2> --\>Setpoint |<f3> --\>dt |<f4> [Kp=15.000] |<f5> [Ki=0.000] |<f6> [Kd=0.200]"] +"Pitch" -> "Pitch PID":f1 [label="Constant"] +"RC Pitch" -> "Pitch PID":f2 [label="Constant"] +"Ts_angle" -> "Pitch PID":f3 [label="Constant"] +"Yaw PID"[shape=record +label="<f0>Yaw PID |<f1> --\>Cur point |<f2> --\>Setpoint |<f3> --\>dt |<f4> [Kp=2.600] |<f5> [Ki=0.000] |<f6> [Kd=0.000]"] +"Yaw" -> "Yaw PID":f1 [label="Constant"] +"Yaw Setpoint" -> "Yaw PID":f2 [label="Constant"] +"Ts_angle" -> "Yaw PID":f3 [label="Constant"] +"Roll Rate PID"[shape=record +label="<f0>Roll Rate PID |<f1> --\>Cur point |<f2> --\>Setpoint |<f3> --\>dt |<f4> [Kp=4600.000] |<f5> [Ki=0.000] |<f6> [Kd=550.000]"] +"dPhi" -> "Roll Rate PID":f1 [label="Constant"] +"Roll PID" -> "Roll Rate PID":f2 [label="Correction"] +"Ts_angle" -> "Roll Rate PID":f3 [label="Constant"] +"Pitch Rate PID"[shape=record +label="<f0>Pitch Rate PID |<f1> --\>Cur point |<f2> --\>Setpoint |<f3> --\>dt |<f4> [Kp=4600.000] |<f5> [Ki=0.000] |<f6> [Kd=550.000]"] +"dTheta" -> "Pitch Rate PID":f1 [label="Constant"] +"Pitch PID" -> "Pitch Rate PID":f2 [label="Correction"] +"Ts_angle" -> "Pitch Rate PID":f3 [label="Constant"] +"Yaw Rate PID"[shape=record +label="<f0>Yaw Rate PID |<f1> --\>Cur point |<f2> --\>Setpoint |<f3> --\>dt |<f4> [Kp=435480.000] |<f5> [Ki=0.000] |<f6> [Kd=0.000]"] +"dPsi" -> "Yaw Rate PID":f1 [label="Constant"] +"RC Yaw" -> "Yaw Rate PID":f2 [label="Constant"] +"Ts_angle" -> "Yaw Rate PID":f3 [label="Constant"] +"X pos PID"[shape=record +label="<f0>X pos PID |<f1> --\>Cur point |<f2> --\>Setpoint |<f3> --\>dt |<f4> [Kp=-0.015] |<f5> [Ki=-0.005] |<f6> [Kd=-0.030]"] +"VRPN X" -> "X pos PID":f1 [label="Constant"] +"X Setpoint" -> "X pos PID":f2 [label="Constant"] +"Ts_position" -> "X pos PID":f3 [label="Constant"] +"Y pos PID"[shape=record +label="<f0>Y pos PID |<f1> --\>Cur point |<f2> --\>Setpoint |<f3> --\>dt |<f4> [Kp=0.015] |<f5> [Ki=0.005] |<f6> [Kd=0.030]"] +"VRPN Y" -> "Y pos PID":f1 [label="Constant"] +"Y Setpoint" -> "Y pos PID":f2 [label="Constant"] +"Ts_position" -> "Y pos PID":f3 [label="Constant"] +"Altitude PID"[shape=record +label="<f0>Altitude PID |<f1> --\>Cur point |<f2> --\>Setpoint |<f3> --\>dt |<f4> [Kp=9804.000] |<f5> [Ki=817.000] |<f6> [Kd=7353.000]"] +"VRPN Alt" -> "Altitude PID":f1 [label="Constant"] +"Alt Setpoint" -> "Altitude PID":f2 [label="Constant"] +"Ts_position" -> "Altitude PID":f3 [label="Constant"] +"X Setpoint"[shape=record +label="<f0>X Setpoint |<f1> [Constant=0.000]"] +"Y Setpoint"[shape=record +label="<f0>Y Setpoint |<f1> [Constant=0.000]"] +"Alt Setpoint"[shape=record +label="<f0>Alt Setpoint |<f1> [Constant=0.000]"] +"Yaw Setpoint"[shape=record +label="<f0>Yaw Setpoint |<f1> [Constant=0.000]"] +"Throttle trim"[shape=record +label="<f0>Throttle trim |<f1> [Constant=0.000]"] +"T trim add"[shape=record +label="<f0>T trim add |<f1> --\>Summand 1 |<f2> --\>Summand 2"] +"Altitude PID" -> "T trim add":f1 [label="Correction"] +"Throttle trim" -> "T trim add":f2 [label="Constant"] +"Pitch"[shape=record +label="<f0>Pitch |<f1> [Constant=0.000]"] +"Roll"[shape=record +label="<f0>Roll |<f1> [Constant=0.000]"] +"Yaw"[shape=record +label="<f0>Yaw |<f1> [Constant=0.000]"] +"dTheta"[shape=record +label="<f0>dTheta |<f1> [Constant=0.000]"] +"dPhi"[shape=record +label="<f0>dPhi |<f1> [Constant=0.000]"] +"dPsi"[shape=record +label="<f0>dPsi |<f1> [Constant=0.000]"] +"P PWM Clamp"[shape=record +label="<f0>P PWM Clamp |<f1> --\>Bounds in |<f2> [Min=-20000.000] |<f3> [Max=20000.000]"] +"Pitch Rate PID" -> "P PWM Clamp":f1 [label="Correction"] +"R PWM Clamp"[shape=record +label="<f0>R PWM Clamp |<f1> --\>Bounds in |<f2> [Min=-20000.000] |<f3> [Max=20000.000]"] +"Roll Rate PID" -> "R PWM Clamp":f1 [label="Correction"] +"Y PWM Clamp"[shape=record +label="<f0>Y PWM Clamp |<f1> --\>Bounds in |<f2> [Min=-20000.000] |<f3> [Max=20000.000]"] +"Yaw Rate PID" -> "Y PWM Clamp":f1 [label="Correction"] +"VRPN X"[shape=record +label="<f0>VRPN X |<f1> [Constant=0.000]"] +"VRPN Y"[shape=record +label="<f0>VRPN Y |<f1> [Constant=0.000]"] +"VRPN Alt"[shape=record +label="<f0>VRPN Alt |<f1> [Constant=0.000]"] +"VRPN Pitch"[shape=record +label="<f0>VRPN Pitch |<f1> [Constant=0.000]"] +"VRPN Roll"[shape=record +label="<f0>VRPN Roll |<f1> [Constant=0.000]"] +"RC Pitch"[shape=record +label="<f0>RC Pitch |<f1> [Constant=0.000]"] +"RC Roll"[shape=record +label="<f0>RC Roll |<f1> [Constant=0.000]"] +"RC Yaw"[shape=record +label="<f0>RC Yaw |<f1> [Constant=0.000]"] +"RC Throttle"[shape=record +label="<f0>RC Throttle |<f1> [Constant=0.000]"] +"Signal Mixer"[shape=record +label="<f0>Signal Mixer |<f1> --\>Throttle |<f2> --\>Pitch |<f3> --\>Roll |<f4> --\>Yaw"] +"RC Throttle" -> "Signal Mixer":f1 [label="Constant"] +"P PWM Clamp" -> "Signal Mixer":f2 [label="Bounded"] +"R PWM Clamp" -> "Signal Mixer":f3 [label="Bounded"] +"Y PWM Clamp" -> "Signal Mixer":f4 [label="Bounded"] +"Ts_angle"[shape=record +label="<f0>Ts_angle |<f1> [Constant=0.005]"] +"Ts_position"[shape=record +label="<f0>Ts_position |<f1> [Constant=0.100]"] +} \ No newline at end of file diff --git a/quad/sw/modular_quad_pid/gen_diagram/network.png b/quad/sw/modular_quad_pid/gen_diagram/network.png new file mode 100644 index 0000000000000000000000000000000000000000..47437dd878b0765f9547545d6b4ba736cad20810 Binary files /dev/null and b/quad/sw/modular_quad_pid/gen_diagram/network.png differ diff --git a/quad/sw/modular_quad_pid/src/PID.h b/quad/sw/modular_quad_pid/src/PID.h index d0e69ddcc9450d9888b8e7a424e45aabb46b2039..a7701ab2a8a8701343d4e65d5688759d70aba2b9 100644 --- a/quad/sw/modular_quad_pid/src/PID.h +++ b/quad/sw/modular_quad_pid/src/PID.h @@ -81,7 +81,4 @@ #define ALT_ZPOS_KI 817.0f #define ALT_ZPOS_KD 7353.0f -// Computes control error and correction -PID_values pid_computation(PID_t *pid); - #endif /* PID_H_ */ diff --git a/quad/sw/modular_quad_pid/src/actuator_command_processing.c b/quad/sw/modular_quad_pid/src/actuator_command_processing.c index 64fce135a7eb95bb5a80962e72d64572dbccf574..e27dbf434d1aba16341a67fc3115f3ea2d88725f 100644 --- a/quad/sw/modular_quad_pid/src/actuator_command_processing.c +++ b/quad/sw/modular_quad_pid/src/actuator_command_processing.c @@ -7,6 +7,7 @@ #include "actuator_command_processing.h" #include "sensor_processing.h" +#include "controllers.h" int actuator_command_processing(log_t* log_struct, user_input_t * user_input_struct, raw_actuator_t* raw_actuator_struct, actuator_command_t* actuator_command_struct) { diff --git a/quad/sw/modular_quad_pid/src/callbacks.c b/quad/sw/modular_quad_pid/src/callbacks.c index 0ae4320d078fd749d269e3147eb211d51177fc25..36f393434b9da690ec03e2532e415055955f1719 100644 --- a/quad/sw/modular_quad_pid/src/callbacks.c +++ b/quad/sw/modular_quad_pid/src/callbacks.c @@ -2,6 +2,7 @@ #include "commands.h" #include "type_def.h" #include "uart.h" +#include "computation_graph.h" /* * Static variables used to keep track of packet counts @@ -15,8 +16,17 @@ static size_t total_payload_received = 0; /** * Currently does nothing. */ -int debug(modular_structs_t *structs) +int cb_debug(modular_structs_t *structs) { + char buf[255]; + + // Get the node ID, parameter ID, parameter value + u8 node_id = uart_buff_data_get_u8(0); + struct computation_graph* graph = structs->parameter_struct.graph; + float param_val = graph_get_output(graph, node_id, 0); + + int length = snprintf(buf, sizeof buf, "%f", param_val); + send_data(DEBUG_ID, 0, buf, length >= sizeof(buf) ? 255 : length + 1); return 0; } @@ -116,34 +126,14 @@ int cb_setparam(modular_structs_t *structs) { return -1; } + struct computation_graph* graph = structs->parameter_struct.graph; - // Get the controller ID, parameter ID, parameter value - u8 controller_id = uart_buff_data_get_u8(0); + // Get the node ID, parameter ID, parameter value + u8 node_id = uart_buff_data_get_u8(0); u8 param_id = uart_buff_data_get_u8(1); float param_val = uart_buff_data_get_float(2); - // Check to make sure the IDs are in bounds - if (controller_id >= MAX_CONTROLLER_ID || - param_id >= MAX_CONTROL_PARAM_ID) - { - return -1; - } - - // Set the param_val into the controller by controller_id, param_id - switch(param_id) - { - case KP_ID: - structs->parameter_struct.pid_controllers[controller_id].Kp = param_val; - break; - case KI_ID: - structs->parameter_struct.pid_controllers[controller_id].Ki = param_val; - break; - case KD_ID: - structs->parameter_struct.pid_controllers[controller_id].Kd = param_val; - break; - case SP_ID: - structs->parameter_struct.pid_controllers[controller_id].setpoint = param_val; - break; - } + // Set the value for that parameter on that node + graph_set_param_val(graph, node_id, param_id, param_val); return 0; } @@ -184,39 +174,15 @@ int cb_getparam(modular_structs_t* structs) } // Get the controller ID, parameter ID - u8 controller_id = uart_buff_data_get_u8(0); + u8 node_id = uart_buff_data_get_u8(0); u8 param_id = uart_buff_data_get_u8(1); - // Check to make sure the IDs are in bounds - if (controller_id >= MAX_CONTROLLER_ID || - param_id >= MAX_CONTROL_PARAM_ID) - { - return -1; - } - - // Make the variable to send - float param_val; - // Set the param_val equal to the parameter value stored in the controller by - // controller_id, param_id - switch(param_id) - { - case KP_ID: - param_val = structs->parameter_struct.pid_controllers[controller_id].Kp; - break; - case KI_ID: - param_val = structs->parameter_struct.pid_controllers[controller_id].Ki; - break; - case KD_ID: - param_val = structs->parameter_struct.pid_controllers[controller_id].Kd; - break; - case SP_ID: - param_val = structs->parameter_struct.pid_controllers[controller_id].setpoint; - break; - } + struct computation_graph* graph = structs->parameter_struct.graph; + float param_val = graph_get_param_val(graph, node_id, param_id); // Format the response data char resp_data[6]; // Controller ID - resp_data[0] = controller_id; + resp_data[0] = node_id; // Parameter ID resp_data[1] = param_id; // Parameter value (4 byte float) diff --git a/quad/sw/modular_quad_pid/src/computation_graph.c b/quad/sw/modular_quad_pid/src/computation_graph.c new file mode 120000 index 0000000000000000000000000000000000000000..c9f4c52019cf7cf8763b84aa9954a943f37ba2a1 --- /dev/null +++ b/quad/sw/modular_quad_pid/src/computation_graph.c @@ -0,0 +1 @@ +../../../computation_graph/src/computation_graph.c \ No newline at end of file diff --git a/quad/sw/modular_quad_pid/src/computation_graph.h b/quad/sw/modular_quad_pid/src/computation_graph.h new file mode 120000 index 0000000000000000000000000000000000000000..eb8fe7bfea6e123f35220c668690d99d290daa72 --- /dev/null +++ b/quad/sw/modular_quad_pid/src/computation_graph.h @@ -0,0 +1 @@ +../../../computation_graph/src/computation_graph.h \ No newline at end of file diff --git a/quad/sw/modular_quad_pid/src/control_algorithm.c b/quad/sw/modular_quad_pid/src/control_algorithm.c index 202e035b3740c87ec7513938bb853c62ddec5946..e54a4101d4649bde1c488423339ada0672121ac8 100644 --- a/quad/sw/modular_quad_pid/src/control_algorithm.c +++ b/quad/sw/modular_quad_pid/src/control_algorithm.c @@ -6,59 +6,202 @@ */ // This implemented modular quadrotor software implements a PID control algorithm - + #include "control_algorithm.h" -#include "communication.h" +#include "graph_blocks/node_pid.h" +#include "graph_blocks/node_bounds.h" +#include "graph_blocks/node_constant.h" +#include "graph_blocks/node_mixer.h" +#include "graph_blocks/node_add.h" +#include "PID.h" +#include "util.h" #define ROLL_PITCH_MAX_ANGLE 1.5708 // 90 degrees #define PWM_DIFF_BOUNDS 30000 - int control_algorithm_init(parameter_t * parameter_struct) - { - // HUMAN Piloted (RC) PID DEFINITIONS ////// - // RC PIDs for roll (2 loops: angle --> angular velocity) - parameter_struct->pid_controllers[ROLL_ID].dt = 0.005; parameter_struct->pid_controllers[ROLL_RATE_ID].dt = 0.005; // 5 ms calculation period - - // RC PIDs for pitch (2 loops: angle --> angular velocity) - parameter_struct->pid_controllers[PITCH_ID].dt = 0.005; parameter_struct->pid_controllers[PITCH_RATE_ID].dt = 0.005; // 5 ms calculation period - - // initialize Yaw PID_t and PID constants - // RC PID for yaw (1 loop angular velocity) - parameter_struct->pid_controllers[YAW_RATE_ID].dt = 0.005; // 5 ms calculation period - - // AUTOMATIC Pilot (Position) PID DEFINITIONS ////// - // Local X PID using a translation from X camera system data to quad local X position (3 loops: local y position --> angle --> angular velocity) - parameter_struct->pid_controllers[LOCAL_X_ID].dt = 0.100; - - // Local Y PID using a translation from Y camera system data to quad local Y position(3 loops: local x position --> angle --> angular velocity) - parameter_struct->pid_controllers[LOCAL_Y_ID].dt = 0.100; - - // CAM PIDs for yaw (2 loops angle --> angular velocity) - parameter_struct->pid_controllers[YAW_ID].dt = 0.100; - - // CAM PID for altitude (1 loop altitude) - parameter_struct->pid_controllers[ALT_ID].dt = 0.100; - - // PID coeffiecients (Position) - setPIDCoeff(&(parameter_struct->pid_controllers[LOCAL_Y_ID]), YPOS_KP, YPOS_KI, YPOS_KD); - setPIDCoeff(&(parameter_struct->pid_controllers[LOCAL_X_ID]), XPOS_KP, XPOS_KI, XPOS_KD); - setPIDCoeff(&(parameter_struct->pid_controllers[ALT_ID]), ALT_ZPOS_KP, ALT_ZPOS_KI, ALT_ZPOS_KD); - - // PID coefficients (Angle) - setPIDCoeff(&(parameter_struct->pid_controllers[PITCH_ID]), PITCH_ANGLE_KP, PITCH_ANGLE_KI, PITCH_ANGLE_KD); - setPIDCoeff(&(parameter_struct->pid_controllers[ROLL_ID]), ROLL_ANGLE_KP, ROLL_ANGLE_KI, ROLL_ANGLE_KD); - setPIDCoeff(&(parameter_struct->pid_controllers[YAW_ID]), YAW_ANGLE_KP, YAW_ANGLE_KI, YAW_ANGLE_KD); - - // PID coefficients (Angular Velocity) - setPIDCoeff(&(parameter_struct->pid_controllers[PITCH_RATE_ID]), PITCH_ANGULAR_VELOCITY_KP, PITCH_ANGULAR_VELOCITY_KI, PITCH_ANGULAR_VELOCITY_KD); - setPIDCoeff(&(parameter_struct->pid_controllers[ROLL_RATE_ID]), ROLL_ANGULAR_VELOCITY_KP, ROLL_ANGULAR_VELOCITY_KI, ROLL_ANGULAR_VELOCITY_KD); - setPIDCoeff(&(parameter_struct->pid_controllers[YAW_RATE_ID]), YAW_ANGULAR_VELOCITY_KP, YAW_ANGULAR_VELOCITY_KI, YAW_ANGULAR_VELOCITY_KD); +void connect_autonomous(parameter_t* ps) { + struct computation_graph* graph = ps->graph; + graph_set_source(graph, ps->pitch_pid, PID_SETPOINT, ps->x_pos_pid, PID_CORRECTION); + graph_set_source(graph, ps->roll_pid, PID_SETPOINT, ps->y_pos_pid, PID_CORRECTION); + //graph_set_source(graph, ps->mixer, MIXER_THROTTLE, ps->throttle_trim_add, ADD_SUM); + //graph_set_source(graph, ps->yaw_r_pid, PID_SETPOINT, ps->yaw_pid, PID_CORRECTION); + // TODO: Change this to use VRPN time differences + // NOTE: This is being set here because if we set it in the initialization, it is + // never marked as "updated", and therefore, doesn't get computed. Yeah, I know, that's bad... + graph_set_param_val(graph, ps->pos_time, CONST_SET, 0.1); +} + +void connect_manual(parameter_t* ps) { + struct computation_graph* graph = ps->graph; + graph_set_source(graph, ps->pitch_pid, PID_SETPOINT, ps->rc_pitch, CONST_VAL); + graph_set_source(graph, ps->roll_pid, PID_SETPOINT, ps->rc_roll, CONST_VAL); + graph_set_source(graph, ps->mixer, MIXER_THROTTLE, ps->rc_throttle, CONST_VAL); + graph_set_source(graph, ps->yaw_r_pid, PID_SETPOINT, ps->rc_yaw, CONST_VAL); + // TODO: Change this to use LOOP_TIME + graph_set_param_val(graph, ps->angle_time, CONST_SET, 0.005); +} + +int control_algorithm_init(parameter_t * ps) +{ + struct computation_graph* graph = create_graph(); + ps->graph = graph; + + // Create all the PID blocks + ps->roll_pid = graph_add_node_pid(graph, "Roll PID"); + ps->pitch_pid = graph_add_node_pid(graph, "Pitch PID"); + ps->yaw_pid = graph_add_node_pid(graph, "Yaw PID"); + ps->roll_r_pid = graph_add_node_pid(graph, "Roll Rate PID"); + ps->pitch_r_pid = graph_add_node_pid(graph, "Pitch Rate PID"); + ps->yaw_r_pid = graph_add_node_pid(graph, "Yaw Rate PID"); + ps->x_pos_pid = graph_add_node_pid(graph, "X pos PID"); + ps->y_pos_pid = graph_add_node_pid(graph, "Y pos PID"); + ps->alt_pid = graph_add_node_pid(graph, "Altitude PID"); + ps->x_set = graph_add_node_const(graph, "X Setpoint"); // ID 9 + ps->y_set = graph_add_node_const(graph, "Y Setpoint"); + ps->alt_set = graph_add_node_const(graph, "Alt Setpoint"); + ps->yaw_set = graph_add_node_const(graph, "Yaw Setpoint"); + ps->throttle_trim = graph_add_node_const(graph, "Throttle trim"); + ps->throttle_trim_add = graph_add_node_add(graph, "T trim add"); + + // Create blocks for sensor inputs + ps->cur_pitch = graph_add_node_const(graph, "Pitch"); // ID 20 + ps->cur_roll = graph_add_node_const(graph, "Roll"); + ps->cur_yaw = graph_add_node_const(graph, "Yaw"); + // Yaw angular velocity PID + // theta_dot is the angular velocity about the y-axis + // phi_dot is the angular velocity about the x-axis + // psi_dot is the angular velocity about the z-axis + // These are calculated from using the gimbal equations + ps->theta_dot = graph_add_node_const(graph, "dTheta"); + ps->phi_dot = graph_add_node_const(graph, "dPhi"); + ps->psi_dot = graph_add_node_const(graph, "dPsi"); + ps->clamp_d_pwmP = graph_add_node_bounds(graph, "P PWM Clamp"); + ps->clamp_d_pwmR = graph_add_node_bounds(graph, "R PWM Clamp"); + ps->clamp_d_pwmY = graph_add_node_bounds(graph, "Y PWM Clamp"); + + // Create blocks for VRPN data + ps->vrpn_x = graph_add_node_const(graph, "VRPN X"); + ps->vrpn_y = graph_add_node_const(graph, "VRPN Y"); + ps->vrpn_alt = graph_add_node_const(graph, "VRPN Alt"); + ps->vrpn_pitch = graph_add_node_const(graph, "VRPN Pitch"); + ps->vrpn_roll = graph_add_node_const(graph, "VRPN Roll"); + + // Create blocks for RC controller + ps->rc_pitch = graph_add_node_const(graph, "RC Pitch"); + ps->rc_roll = graph_add_node_const(graph, "RC Roll"); + ps->rc_yaw = graph_add_node_const(graph, "RC Yaw"); + ps->rc_throttle = graph_add_node_const(graph, "RC Throttle"); + + ps->mixer = graph_add_node_mixer(graph, "Signal Mixer"); + + ps->angle_time = graph_add_node_const(graph, "Ts_angle"); + ps->pos_time = graph_add_node_const(graph, "Ts_position"); + + // Connect pitch PID chain + graph_set_source(graph, ps->pitch_r_pid, PID_SETPOINT, ps->pitch_pid, PID_CORRECTION); + graph_set_source(graph, ps->pitch_r_pid, PID_CUR_POINT, ps->theta_dot, CONST_VAL); + graph_set_source(graph, ps->pitch_r_pid, PID_DT, ps->angle_time, CONST_VAL); + //graph_set_source(graph, ps->pitch_pid, PID_SETPOINT, ps->rc_pitch, CONST_VAL); + graph_set_source(graph, ps->pitch_pid, PID_CUR_POINT, ps->cur_pitch, CONST_VAL); + graph_set_source(graph, ps->pitch_pid, PID_DT, ps->angle_time, CONST_VAL); + + // Connect roll PID chain + graph_set_source(graph, ps->roll_r_pid, PID_SETPOINT, ps->roll_pid, PID_CORRECTION); + graph_set_source(graph, ps->roll_r_pid, PID_CUR_POINT, ps->phi_dot, CONST_VAL); + graph_set_source(graph, ps->roll_r_pid, PID_DT, ps->angle_time, CONST_VAL); + //graph_set_source(graph, ps->roll_pid, PID_SETPOINT, ps->rc_roll, CONST_VAL); + graph_set_source(graph, ps->roll_pid, PID_CUR_POINT, ps->cur_roll, CONST_VAL); + graph_set_source(graph, ps->roll_pid, PID_DT, ps->angle_time, CONST_VAL); + + // Connect yaw PID chain + graph_set_source(graph, ps->yaw_r_pid, PID_SETPOINT, ps->rc_yaw, PID_CORRECTION); + graph_set_source(graph, ps->yaw_r_pid, PID_CUR_POINT, ps->psi_dot, CONST_VAL); + graph_set_source(graph, ps->yaw_r_pid, PID_DT, ps->angle_time, CONST_VAL); + + + // X autonomous + graph_set_source(graph, ps->x_pos_pid, PID_DT, ps->pos_time, CONST_VAL); + graph_set_source(graph, ps->x_pos_pid, PID_CUR_POINT, ps->vrpn_x, CONST_VAL); + graph_set_source(graph, ps->x_pos_pid, PID_SETPOINT, ps->x_set, CONST_VAL); + // Y autonomous + graph_set_source(graph, ps->y_pos_pid, PID_DT, ps->pos_time, CONST_VAL); + graph_set_source(graph, ps->y_pos_pid, PID_CUR_POINT, ps->vrpn_y, CONST_VAL); + graph_set_source(graph, ps->y_pos_pid, PID_SETPOINT, ps->y_set, CONST_VAL); + // Alt autonomous + graph_set_source(graph, ps->alt_pid, PID_DT, ps->pos_time, CONST_VAL); + graph_set_source(graph, ps->alt_pid, PID_CUR_POINT, ps->vrpn_alt, CONST_VAL); + graph_set_source(graph, ps->alt_pid, PID_SETPOINT, ps->alt_set, CONST_VAL); + graph_set_source(graph, ps->alt_set, CONST_VAL, ps->vrpn_alt, CONST_VAL); + graph_set_source(graph, ps->throttle_trim_add, ADD_SUMMAND1, ps->alt_pid, PID_CORRECTION); + graph_set_source(graph, ps->throttle_trim_add, ADD_SUMMAND2, ps->throttle_trim, CONST_VAL); + // Yaw autonomous + graph_set_source(graph, ps->yaw_pid, PID_DT, ps->angle_time, CONST_VAL); + graph_set_source(graph, ps->yaw_pid, PID_SETPOINT, ps->yaw_set, CONST_VAL); + graph_set_source(graph, ps->yaw_pid, PID_CUR_POINT, ps->cur_yaw, CONST_VAL); + + // Connect PWM clamping blocks + graph_set_source(graph, ps->clamp_d_pwmP, BOUNDS_IN, ps->pitch_r_pid, PID_CORRECTION); + graph_set_source(graph, ps->clamp_d_pwmR, BOUNDS_IN, ps->roll_r_pid, PID_CORRECTION); + graph_set_source(graph, ps->clamp_d_pwmY, BOUNDS_IN, ps->yaw_r_pid, PID_CORRECTION); + + // Connect signal mixer + //graph_set_source(graph, ps->mixer, MIXER_THROTTLE, ps->rc_throttle, CONST_VAL); + graph_set_source(graph, ps->mixer, MIXER_PITCH, ps->clamp_d_pwmP, BOUNDS_OUT); + graph_set_source(graph, ps->mixer, MIXER_ROLL, ps->clamp_d_pwmR, BOUNDS_OUT); + graph_set_source(graph, ps->mixer, MIXER_YAW, ps->clamp_d_pwmY, BOUNDS_OUT); + + // Set pitch PID constants + graph_set_param_val(graph, ps->pitch_pid, PID_KP, PITCH_ANGLE_KP); + graph_set_param_val(graph, ps->pitch_pid, PID_KI, PITCH_ANGLE_KI); + graph_set_param_val(graph, ps->pitch_pid, PID_KD, PITCH_ANGLE_KD); + graph_set_param_val(graph, ps->pitch_r_pid, PID_KP, PITCH_ANGULAR_VELOCITY_KP); + graph_set_param_val(graph, ps->pitch_r_pid, PID_KI, PITCH_ANGULAR_VELOCITY_KI); + graph_set_param_val(graph, ps->pitch_r_pid, PID_KD, PITCH_ANGULAR_VELOCITY_KD); + + // Set roll PID constants + graph_set_param_val(graph, ps->roll_pid, PID_KP, ROLL_ANGLE_KP); + graph_set_param_val(graph, ps->roll_pid, PID_KI, ROLL_ANGLE_KI); + graph_set_param_val(graph, ps->roll_pid, PID_KD, ROLL_ANGLE_KD); + graph_set_param_val(graph, ps->roll_r_pid, PID_KP, ROLL_ANGULAR_VELOCITY_KP); + graph_set_param_val(graph, ps->roll_r_pid, PID_KI, ROLL_ANGULAR_VELOCITY_KI); + graph_set_param_val(graph, ps->roll_r_pid, PID_KD, ROLL_ANGULAR_VELOCITY_KD); + + // Set yaw PID constants + graph_set_param_val(graph, ps->yaw_pid, PID_KP, YAW_ANGLE_KP); + graph_set_param_val(graph, ps->yaw_pid, PID_KI, YAW_ANGLE_KI); + graph_set_param_val(graph, ps->yaw_pid, PID_KD, YAW_ANGLE_KD); + graph_set_param_val(graph, ps->yaw_r_pid, PID_KP, YAW_ANGULAR_VELOCITY_KP); + graph_set_param_val(graph, ps->yaw_r_pid, PID_KI, YAW_ANGULAR_VELOCITY_KI); + graph_set_param_val(graph, ps->yaw_r_pid, PID_KD, YAW_ANGULAR_VELOCITY_KD); + + // Set X/Y/Alt constants + graph_set_param_val(graph, ps->x_pos_pid, PID_KP, XPOS_KP); + graph_set_param_val(graph, ps->x_pos_pid, PID_KI, XPOS_KI); + graph_set_param_val(graph, ps->x_pos_pid, PID_KD, XPOS_KD); + graph_set_param_val(graph, ps->y_pos_pid, PID_KP, YPOS_KP); + graph_set_param_val(graph, ps->y_pos_pid, PID_KI, YPOS_KI); + graph_set_param_val(graph, ps->y_pos_pid, PID_KD, YPOS_KD); + graph_set_param_val(graph, ps->alt_pid, PID_KP, ALT_ZPOS_KP); + graph_set_param_val(graph, ps->alt_pid, PID_KI, ALT_ZPOS_KI); + graph_set_param_val(graph, ps->alt_pid, PID_KD, ALT_ZPOS_KD); + + // Set PWM difference clamping limits + graph_set_param_val(graph, ps->clamp_d_pwmP, BOUNDS_MIN, -20000); + graph_set_param_val(graph, ps->clamp_d_pwmP, BOUNDS_MAX, 20000); + graph_set_param_val(graph, ps->clamp_d_pwmR, BOUNDS_MIN, -20000); + graph_set_param_val(graph, ps->clamp_d_pwmR, BOUNDS_MAX, 20000); + graph_set_param_val(graph, ps->clamp_d_pwmY, BOUNDS_MIN, -20000); + graph_set_param_val(graph, ps->clamp_d_pwmY, BOUNDS_MAX, 20000); + + // Set initial mode + connect_manual(ps); return 0; } - - int control_algorithm(log_t* log_struct, user_input_t * user_input_struct, sensor_t* sensor_struct, setpoint_t* setpoint_struct, parameter_t* parameter_struct, user_defined_t* user_defined_struct, raw_actuator_t* raw_actuator_struct, modular_structs_t* structs) + + int control_algorithm(log_t* log_struct, user_input_t * user_input_struct, sensor_t* sensor_struct, setpoint_t* setpoint_struct, parameter_t* ps, user_defined_t* user_defined_struct, actuator_command_t* actuator_struct, modular_structs_t* structs) { + struct computation_graph* graph = ps->graph; // use the 'flap' switch as the flight mode selector int cur_fm_switch = read_flap(user_input_struct->rc_commands[FLAP]); static int last_fm_switch = MANUAL_FLIGHT_MODE; @@ -66,31 +209,19 @@ // reset flight_mode to MANUAL right away if the flap switch is in manual position // to engage AUTO mode the code waits for a new packet after the flap is switched to auto // before actually engaging AUTO mode - if(cur_fm_switch == MANUAL_FLIGHT_MODE) + if(cur_fm_switch == MANUAL_FLIGHT_MODE) { + if (last_fm_switch == AUTO_FLIGHT_MODE) { + connect_manual(ps); + } user_defined_struct->flight_mode = MANUAL_FLIGHT_MODE; - - static float roll_trim = 0.0; - static float pitch_trim = 0.0; + } // flap switch was just toggled to auto flight mode if((last_fm_switch != cur_fm_switch) && (cur_fm_switch == AUTO_FLIGHT_MODE)) { user_defined_struct->engaging_auto = 1; - // Read in trimmed values because it should read trim values right when the pilot flips the flight mode switch - pitch_trim = user_input_struct->pitch_angle_manual_setpoint; //rc_commands[PITCH] - PITCH_CENTER; - roll_trim = user_input_struct->roll_angle_manual_setpoint; //rc_commands[ROLL] - ROLL_CENTER; - //sensor_struct->trimmedRCValues.yaw = yaw_manual_setpoint; //rc_commands[YAW] - YAW_CENTER; - -// sensor_struct->trims.roll = raw_actuator_struct->controller_corrected_motor_commands[ROLL]; -// sensor_struct->trims.pitch = raw_actuator_struct->controller_corrected_motor_commands[PITCH]; -// sensor_struct->trims.yaw = raw_actuator_struct->controller_corrected_motor_commands[YAW]; - sensor_struct->trims.throttle = user_input_struct->rc_commands[THROTTLE]; - - log_struct->trims.roll = sensor_struct->trims.roll; - log_struct->trims.pitch = sensor_struct->trims.pitch; - log_struct->trims.yaw = sensor_struct->trims.yaw; - log_struct->trims.throttle = sensor_struct->trims.throttle; + graph_set_param_val(graph, ps->throttle_trim, CONST_SET, user_input_struct->rc_commands[THROTTLE]); } if(user_input_struct->locationFresh && user_defined_struct->engaging_auto == 1) @@ -102,27 +233,17 @@ // also reset the previous error and accumulated error from the position PIDs if((cur_fm_switch == AUTO_FLIGHT_MODE) && (user_defined_struct->engaging_auto == 2)) { - // zero out the accumulated error so the I terms don't cause wild things to happen - parameter_struct->pid_controllers[ALT_ID].acc_error = 0.0; - parameter_struct->pid_controllers[LOCAL_X_ID].acc_error = 0.0; - parameter_struct->pid_controllers[LOCAL_Y_ID].acc_error = 0.0; - - // make previous error equal to the current so the D term doesn't spike - parameter_struct->pid_controllers[ALT_ID].prev_error = 0.0; - parameter_struct->pid_controllers[LOCAL_X_ID].prev_error = 0.0; - parameter_struct->pid_controllers[LOCAL_Y_ID].prev_error = 0.0; - - setpoint_struct->desiredQuadPosition.alt_pos = sensor_struct->currentQuadPosition.alt_pos; - setpoint_struct->desiredQuadPosition.x_pos = sensor_struct->currentQuadPosition.x_pos; - setpoint_struct->desiredQuadPosition.y_pos = sensor_struct->currentQuadPosition.y_pos; - setpoint_struct->desiredQuadPosition.yaw = 0.0;//currentQuadPosition.yaw; + graph_set_param_val(graph, ps->x_set, CONST_SET, sensor_struct->currentQuadPosition.x_pos); + graph_set_param_val(graph, ps->y_set, CONST_SET, sensor_struct->currentQuadPosition.y_pos); + graph_set_param_val(graph, ps->alt_set, CONST_SET, sensor_struct->currentQuadPosition.alt_pos); + graph_set_param_val(graph, ps->yaw_set, CONST_SET, sensor_struct->currentQuadPosition.yaw); // reset the flag that engages auto mode user_defined_struct->engaging_auto = 0; - // finally engage the AUTO_FLIGHT_MODE // this ensures that we've gotten a new update packet right after the switch was set to auto mode user_defined_struct->flight_mode = AUTO_FLIGHT_MODE; + connect_autonomous(ps); } //PIDS/////////////////////////////////////////////////////////////////////// @@ -132,203 +253,56 @@ * a pitch or roll for the angle loop PIDs */ -// static int counter_between_packets = 0; - if(user_input_struct->locationFresh) { - parameter_struct->pid_controllers[LOCAL_Y_ID].current_point = sensor_struct->currentQuadPosition.y_pos; - parameter_struct->pid_controllers[LOCAL_Y_ID].setpoint = setpoint_struct->desiredQuadPosition.y_pos; - - parameter_struct->pid_controllers[LOCAL_X_ID].current_point = sensor_struct->currentQuadPosition.x_pos; - parameter_struct->pid_controllers[LOCAL_X_ID].setpoint = setpoint_struct->desiredQuadPosition.x_pos; - - parameter_struct->pid_controllers[ALT_ID].current_point = sensor_struct->currentQuadPosition.alt_pos; - parameter_struct->pid_controllers[ALT_ID].setpoint = setpoint_struct->desiredQuadPosition.alt_pos; - - //logging and PID computation - log_struct->local_y_PID_values = pid_computation(&(parameter_struct->pid_controllers[LOCAL_Y_ID])); - log_struct->local_x_PID_values = pid_computation(&(parameter_struct->pid_controllers[LOCAL_X_ID])); - log_struct->altitude_PID_values = pid_computation(&(parameter_struct->pid_controllers[ALT_ID])); - - // yaw angular position PID calculation - parameter_struct->pid_controllers[YAW_ID].current_point = sensor_struct->currentQuadPosition.yaw;// in radians - parameter_struct->pid_controllers[YAW_ID].setpoint = setpoint_struct->desiredQuadPosition.yaw; // constant setpoint - - //logging and PID computation - log_struct->angle_yaw_PID_values = pid_computation(&(parameter_struct->pid_controllers[YAW_ID])); - + // VRPN values + graph_set_param_val(graph, ps->vrpn_x, CONST_SET, sensor_struct->currentQuadPosition.x_pos); + graph_set_param_val(graph, ps->vrpn_y, CONST_SET, sensor_struct->currentQuadPosition.y_pos); + graph_set_param_val(graph, ps->vrpn_alt, CONST_SET, sensor_struct->currentQuadPosition.alt_pos); + graph_set_param_val(graph, ps->vrpn_pitch, CONST_SET, sensor_struct->currentQuadPosition.pitch); + graph_set_param_val(graph, ps->vrpn_roll, CONST_SET, sensor_struct->currentQuadPosition.roll); + graph_set_param_val(graph, ps->cur_yaw, CONST_SET, sensor_struct->currentQuadPosition.yaw); } - - /* Angle loop - * Calculates current orientation, and outputs - * a pitch, roll, or yaw velocity for the angular velocity loop PIDs - */ - - //angle boundaries - if(parameter_struct->pid_controllers[LOCAL_X_ID].pid_correction > ROLL_PITCH_MAX_ANGLE) - { - parameter_struct->pid_controllers[LOCAL_X_ID].pid_correction = ROLL_PITCH_MAX_ANGLE; - } - if(parameter_struct->pid_controllers[LOCAL_X_ID].pid_correction < -ROLL_PITCH_MAX_ANGLE) - { - parameter_struct->pid_controllers[LOCAL_X_ID].pid_correction = -ROLL_PITCH_MAX_ANGLE; - } - if(parameter_struct->pid_controllers[LOCAL_Y_ID].pid_correction > ROLL_PITCH_MAX_ANGLE) - { - parameter_struct->pid_controllers[LOCAL_Y_ID].pid_correction = ROLL_PITCH_MAX_ANGLE; - } - if(parameter_struct->pid_controllers[LOCAL_Y_ID].pid_correction < -ROLL_PITCH_MAX_ANGLE) - { - parameter_struct->pid_controllers[LOCAL_Y_ID].pid_correction = -ROLL_PITCH_MAX_ANGLE; - } - - parameter_struct->pid_controllers[PITCH_ID].current_point = sensor_struct->pitch_angle_filtered; - parameter_struct->pid_controllers[PITCH_ID].setpoint = - (user_defined_struct->flight_mode == AUTO_FLIGHT_MODE)? - (parameter_struct->pid_controllers[LOCAL_X_ID].pid_correction) + pitch_trim : user_input_struct->pitch_angle_manual_setpoint; - - parameter_struct->pid_controllers[ROLL_ID].current_point = sensor_struct->roll_angle_filtered; - parameter_struct->pid_controllers[ROLL_ID].setpoint = - (user_defined_struct->flight_mode == AUTO_FLIGHT_MODE)? - (parameter_struct->pid_controllers[LOCAL_Y_ID].pid_correction) + roll_trim : user_input_struct->roll_angle_manual_setpoint; - - - //logging and PID computation - log_struct->angle_pitch_PID_values = pid_computation(&(parameter_struct->pid_controllers[PITCH_ID])); - log_struct->angle_roll_PID_values = pid_computation(&(parameter_struct->pid_controllers[ROLL_ID])); - - - /* Angular Velocity Loop - * Takes the desired angular velocity from the angle loop, - * and calculates a PID correction with the current angular velocity - */ - - // theta_dot is the angular velocity about the y-axis - // it is calculated from using the gimbal equations - parameter_struct->pid_controllers[PITCH_RATE_ID].current_point = sensor_struct->theta_dot; - parameter_struct->pid_controllers[PITCH_RATE_ID].setpoint = parameter_struct->pid_controllers[PITCH_ID].pid_correction; - - // phi_dot is the angular velocity about the x-axis - // it is calculated from using the gimbal equations - parameter_struct->pid_controllers[ROLL_RATE_ID].current_point = sensor_struct->phi_dot; - parameter_struct->pid_controllers[ROLL_RATE_ID].setpoint = parameter_struct->pid_controllers[ROLL_ID].pid_correction; - - // Yaw angular velocity PID - // psi_dot is the angular velocity about the z-axis - // it is calculated from using the gimbal equations - parameter_struct->pid_controllers[YAW_RATE_ID].current_point = sensor_struct->psi_dot; - parameter_struct->pid_controllers[YAW_RATE_ID].setpoint = /*(user_defined_struct->flight_mode == AUTO_FLIGHT_MODE)? - parameter_struct->pid_controllers[YAW_ID].pid_correction :*/ user_input_struct->yaw_manual_setpoint; // no trim added because the controller already works well - - //logging and PID computation - log_struct->ang_vel_pitch_PID_values = pid_computation(&(parameter_struct->pid_controllers[PITCH_RATE_ID])); - log_struct->ang_vel_roll_PID_values = pid_computation(&(parameter_struct->pid_controllers[ROLL_RATE_ID])); - log_struct->ang_vel_yaw_PID_values = pid_computation(&(parameter_struct->pid_controllers[YAW_RATE_ID])); - - //END PIDs/////////////////////////////////////////////////////////////////////// - + // Sensor values + graph_set_param_val(graph, ps->cur_pitch, CONST_SET, sensor_struct->pitch_angle_filtered); + graph_set_param_val(graph, ps->cur_roll, CONST_SET, sensor_struct->roll_angle_filtered); + graph_set_param_val(graph, ps->theta_dot, CONST_SET, sensor_struct->theta_dot); + graph_set_param_val(graph, ps->phi_dot, CONST_SET, sensor_struct->phi_dot); + graph_set_param_val(graph, ps->psi_dot, CONST_SET, sensor_struct->psi_dot); + // RC values + graph_set_param_val(graph, ps->rc_pitch, CONST_SET, user_input_struct->pitch_angle_manual_setpoint); + graph_set_param_val(graph, ps->rc_roll, CONST_SET, user_input_struct->roll_angle_manual_setpoint); + graph_set_param_val(graph, ps->rc_yaw, CONST_SET, user_input_struct->yaw_manual_setpoint); + graph_set_param_val(graph, ps->rc_throttle, CONST_SET, user_input_struct->rc_commands[THROTTLE]); + + // Compute VRPN blocks so they can be logged + int outputs[4] = {ps->mixer, ps->vrpn_alt, ps->vrpn_pitch, ps->vrpn_roll}; + graph_compute_nodes(graph, outputs, 4); // here for now so in case any flight command is not PID controlled, it will default to rc_command value: - memcpy(raw_actuator_struct->controller_corrected_motor_commands, user_input_struct->rc_commands, sizeof(int) * 6); + //memcpy(raw_actuator_struct->controller_corrected_motor_commands, user_input_struct->rc_commands, sizeof(int) * 6); // don't use the PID corrections if the throttle is less than about 10% of its range - // NOTE!!!!!!!!!!!!!!!!!!!!!! - // re-enable the check for AUTO_FLIGHT_MODE when autonomous is fully enabled - if((user_input_struct->rc_commands[THROTTLE] > - 118000))// || (user_defined_struct->flight_mode == AUTO_FLIGHT_MODE)) - { - if(user_defined_struct->flight_mode == AUTO_FLIGHT_MODE) - { + // re-enable the check for AUTO_FLIGHT_MODE when autonomous is fully enabled!!! + if((user_input_struct->rc_commands[THROTTLE] > 118000)) + { //THROTTLE - //raw_actuator_struct->controller_corrected_motor_commands[THROTTLE] = - // ((int)(parameter_struct->pid_controllers[ALT_ID].pid_correction)) + sensor_struct->trims.throttle; - - //ROLL - raw_actuator_struct->controller_corrected_motor_commands[ROLL] = - parameter_struct->pid_controllers[ROLL_RATE_ID].pid_correction; // + sensor_struct->trims.roll; - - //PITCH - raw_actuator_struct->controller_corrected_motor_commands[PITCH] = - parameter_struct->pid_controllers[PITCH_RATE_ID].pid_correction; // + sensor_struct->trims.pitch; - - //YAW - raw_actuator_struct->controller_corrected_motor_commands[YAW] = - parameter_struct->pid_controllers[YAW_RATE_ID].pid_correction;// + sensor_struct->trims.yaw; - -// static int slow_down = 0; -// slow_down++; -// if(slow_down % 50 == 0) -// printf("X: %.3f\tY: %.3f\tZ: %.3f\tX_s: %.3f\tX_c: %.3f\tY_s: %.3f\tY_c: %.3f\tZ_s: %.3f\tZ_c: %.3f\t\n", -// parameter_struct->pid_controllers[LOCAL_X_ID].pid_correction, -// parameter_struct->pid_controllers[LOCAL_Y_ID].pid_correction, -// parameter_struct->pid_controllers[ALT_ID].pid_correction, -// parameter_struct->pid_controllers[LOCAL_X_ID].setpoint, parameter_struct->pid_controllers[LOCAL_X_ID].current_point, -// parameter_struct->pid_controllers[LOCAL_Y_ID].setpoint, parameter_struct->pid_controllers[LOCAL_Y_ID].current_point, -// parameter_struct->pid_controllers[ALT_ID].setpoint, parameter_struct->pid_controllers[ALT_ID].current_point); - } - else{ - //ROLL - raw_actuator_struct->controller_corrected_motor_commands[ROLL] = - parameter_struct->pid_controllers[ROLL_RATE_ID].pid_correction; - - //PITCH - raw_actuator_struct->controller_corrected_motor_commands[PITCH] = - parameter_struct->pid_controllers[PITCH_RATE_ID].pid_correction; - - //YAW - raw_actuator_struct->controller_corrected_motor_commands[YAW] = - parameter_struct->pid_controllers[YAW_RATE_ID].pid_correction; - } - - //BOUNDS CHECKING - if(raw_actuator_struct->controller_corrected_motor_commands[THROTTLE] < 0) - raw_actuator_struct->controller_corrected_motor_commands[THROTTLE] = 0; - - //BOUNDS CHECKING - if(raw_actuator_struct->controller_corrected_motor_commands[ROLL] > PWM_DIFF_BOUNDS) - raw_actuator_struct->controller_corrected_motor_commands[ROLL] = PWM_DIFF_BOUNDS; - - if(raw_actuator_struct->controller_corrected_motor_commands[ROLL] < -PWM_DIFF_BOUNDS) - raw_actuator_struct->controller_corrected_motor_commands[ROLL] = -PWM_DIFF_BOUNDS; - - if(raw_actuator_struct->controller_corrected_motor_commands[PITCH] > PWM_DIFF_BOUNDS) - raw_actuator_struct->controller_corrected_motor_commands[PITCH] = PWM_DIFF_BOUNDS; - - if(raw_actuator_struct->controller_corrected_motor_commands[PITCH] < -PWM_DIFF_BOUNDS) - raw_actuator_struct->controller_corrected_motor_commands[PITCH] = -PWM_DIFF_BOUNDS; - - if(raw_actuator_struct->controller_corrected_motor_commands[YAW] > PWM_DIFF_BOUNDS) - raw_actuator_struct->controller_corrected_motor_commands[YAW] = PWM_DIFF_BOUNDS; - - if(raw_actuator_struct->controller_corrected_motor_commands[YAW] < -PWM_DIFF_BOUNDS) - raw_actuator_struct->controller_corrected_motor_commands[YAW] = -PWM_DIFF_BOUNDS; + actuator_struct->pwms[0] = graph_get_output(graph, ps->mixer, MIXER_PWM0); + actuator_struct->pwms[1] = graph_get_output(graph, ps->mixer, MIXER_PWM1); + actuator_struct->pwms[2] = graph_get_output(graph, ps->mixer, MIXER_PWM2); + actuator_struct->pwms[3] = graph_get_output(graph, ps->mixer, MIXER_PWM3); } else { - raw_actuator_struct->controller_corrected_motor_commands[ROLL] = 0; - raw_actuator_struct->controller_corrected_motor_commands[PITCH] = 0; - raw_actuator_struct->controller_corrected_motor_commands[YAW] = 0; + actuator_struct->pwms[0] = user_input_struct->rc_commands[THROTTLE]; + actuator_struct->pwms[1] = user_input_struct->rc_commands[THROTTLE]; + actuator_struct->pwms[2] = user_input_struct->rc_commands[THROTTLE]; + actuator_struct->pwms[3] = user_input_struct->rc_commands[THROTTLE]; } - //logging - // here we are not actually duplicating the logging from the PID computation - // the PID computation logs PID_values struct where this logs the PID struct - // they contain different sets of data - log_struct->local_y_PID = parameter_struct->pid_controllers[LOCAL_Y_ID]; - log_struct->local_x_PID = parameter_struct->pid_controllers[LOCAL_X_ID]; - log_struct->altitude_PID = parameter_struct->pid_controllers[ALT_ID]; - - log_struct->angle_roll_PID = parameter_struct->pid_controllers[ROLL_ID]; - log_struct->angle_pitch_PID = parameter_struct->pid_controllers[PITCH_ID]; - log_struct->angle_yaw_PID = parameter_struct->pid_controllers[YAW_ID]; - - log_struct->ang_vel_roll_PID = parameter_struct->pid_controllers[ROLL_RATE_ID]; - log_struct->ang_vel_pitch_PID = parameter_struct->pid_controllers[PITCH_RATE_ID]; - log_struct->ang_vel_yaw_PID = parameter_struct->pid_controllers[YAW_RATE_ID]; - last_fm_switch = cur_fm_switch; // Make location stale now @@ -336,7 +310,7 @@ return 0; } - + void setPIDCoeff(PID_t* p, float pValue, float iValue, float dValue) { p->Kp = pValue; @@ -344,4 +318,3 @@ p->Kd = dValue; } - diff --git a/quad/sw/modular_quad_pid/src/control_algorithm.h b/quad/sw/modular_quad_pid/src/control_algorithm.h index 463e0f512ef64f3b3d5841d3ed6396db17830579..8e8a15b79b705fe025c57965b45f8f975eadfb1b 100644 --- a/quad/sw/modular_quad_pid/src/control_algorithm.h +++ b/quad/sw/modular_quad_pid/src/control_algorithm.h @@ -57,7 +57,7 @@ int control_algorithm(log_t* log_struct, setpoint_t* setpoint_struct, parameter_t* parameter_struct, user_defined_t* user_defined_struct, - raw_actuator_t* raw_actuator_struct, + actuator_command_t* actuator_struct, modular_structs_t* structs); /** diff --git a/quad/sw/modular_quad_pid/src/graph_blocks/node_add.c b/quad/sw/modular_quad_pid/src/graph_blocks/node_add.c new file mode 120000 index 0000000000000000000000000000000000000000..d7025e0c28ea1028022b77e23924a7734ee55e00 --- /dev/null +++ b/quad/sw/modular_quad_pid/src/graph_blocks/node_add.c @@ -0,0 +1 @@ +../../../../computation_graph/src/graph_blocks/node_add.c \ No newline at end of file diff --git a/quad/sw/modular_quad_pid/src/graph_blocks/node_add.h b/quad/sw/modular_quad_pid/src/graph_blocks/node_add.h new file mode 120000 index 0000000000000000000000000000000000000000..4f4b0ea11eb74e5c45426122afb754f13b098266 --- /dev/null +++ b/quad/sw/modular_quad_pid/src/graph_blocks/node_add.h @@ -0,0 +1 @@ +../../../../computation_graph/src/graph_blocks/node_add.h \ No newline at end of file diff --git a/quad/sw/modular_quad_pid/src/graph_blocks/node_bounds.c b/quad/sw/modular_quad_pid/src/graph_blocks/node_bounds.c new file mode 100644 index 0000000000000000000000000000000000000000..aae4a67957a3d76c89d44480962e7b426b94f235 --- /dev/null +++ b/quad/sw/modular_quad_pid/src/graph_blocks/node_bounds.c @@ -0,0 +1,33 @@ +#include "node_bounds.h" +#include <stdlib.h> + +static void bounds_computation(void *state, const double* params, const double *inputs, double *outputs) { + double to_be_bounded = inputs[BOUNDS_IN]; + if (to_be_bounded < params[BOUNDS_MIN]) { + to_be_bounded = params[BOUNDS_MIN]; + } + if (to_be_bounded > params[BOUNDS_MAX]) { + to_be_bounded = params[BOUNDS_MAX]; + } + outputs[BOUNDS_OUT] = to_be_bounded; +} + +static void reset_bounds(void *state) {} + +static const char* const in_names[1] = {"Bounds in"}; +static const char* const out_names[1] = {"Bounded"}; +static const char* const param_names[2] = {"Min", "Max"}; +const struct graph_node_type node_bounds_type = { + .input_names = in_names, + .output_names = out_names, + .param_names = param_names, + .n_inputs = 1, + .n_outputs = 1, + .n_params = 2, + .execute = bounds_computation, + .reset = reset_bounds +}; + +int graph_add_node_bounds(struct computation_graph *graph, const char* name) { + return graph_add_node(graph, name, &node_bounds_type, NULL); +} diff --git a/quad/sw/modular_quad_pid/src/graph_blocks/node_bounds.h b/quad/sw/modular_quad_pid/src/graph_blocks/node_bounds.h new file mode 100644 index 0000000000000000000000000000000000000000..516ebc28ac065508e77296c0c2942ab35e9249fb --- /dev/null +++ b/quad/sw/modular_quad_pid/src/graph_blocks/node_bounds.h @@ -0,0 +1,22 @@ +#ifndef __NODE_BOUNDS_H__ +#define __NODE_BOUNDS_H__ +#include "../computation_graph.h" + +int graph_add_node_bounds(struct computation_graph *graph, const char* name); + +extern const struct graph_node_type node_bounds_type; + +enum graph_node_bounds_inputs { + BOUNDS_IN, // Input to be bounded +}; + +enum graph_node_bounds_outputs { + BOUNDS_OUT // Bounded output +}; + +enum graph_node_bounds_params { + BOUNDS_MIN, + BOUNDS_MAX +}; + +#endif // __NODE_BOUNDS_H__ diff --git a/quad/sw/modular_quad_pid/src/graph_blocks/node_constant.c b/quad/sw/modular_quad_pid/src/graph_blocks/node_constant.c new file mode 120000 index 0000000000000000000000000000000000000000..5091326ebef1cc7c79bc07820f2069960704809d --- /dev/null +++ b/quad/sw/modular_quad_pid/src/graph_blocks/node_constant.c @@ -0,0 +1 @@ +../../../../computation_graph/src/graph_blocks/node_constant.c \ No newline at end of file diff --git a/quad/sw/modular_quad_pid/src/graph_blocks/node_constant.h b/quad/sw/modular_quad_pid/src/graph_blocks/node_constant.h new file mode 120000 index 0000000000000000000000000000000000000000..d751234bdd8d5b88435e8c5f49c35bc15e55bbf0 --- /dev/null +++ b/quad/sw/modular_quad_pid/src/graph_blocks/node_constant.h @@ -0,0 +1 @@ +../../../../computation_graph/src/graph_blocks/node_constant.h \ No newline at end of file diff --git a/quad/sw/modular_quad_pid/src/graph_blocks/node_mixer.c b/quad/sw/modular_quad_pid/src/graph_blocks/node_mixer.c new file mode 100644 index 0000000000000000000000000000000000000000..8b702b360654b3e957d19743abc24b433f58f64d --- /dev/null +++ b/quad/sw/modular_quad_pid/src/graph_blocks/node_mixer.c @@ -0,0 +1,42 @@ +#include "node_mixer.h" +#include <stdlib.h> + +static int pwm_min = 100000; +static int pwm_max = 200000; + +static int pwm_clamp(int val) { + if (val < pwm_min) {val = pwm_min;} + if (val > pwm_max) {val = pwm_max;} + return val; +} + +static void mixer_computation(void *state, const double* params, const double *inputs, double *outputs) { + int pwm0 = inputs[MIXER_THROTTLE] - inputs[MIXER_PITCH] - inputs[MIXER_ROLL] - inputs[MIXER_YAW]; + int pwm1 = inputs[MIXER_THROTTLE] + inputs[MIXER_PITCH] - inputs[MIXER_ROLL] + inputs[MIXER_YAW]; + int pwm2 = inputs[MIXER_THROTTLE] - inputs[MIXER_PITCH] + inputs[MIXER_ROLL] + inputs[MIXER_YAW]; + int pwm3 = inputs[MIXER_THROTTLE] + inputs[MIXER_PITCH] + inputs[MIXER_ROLL] - inputs[MIXER_YAW]; + outputs[MIXER_PWM0] = pwm_clamp(pwm0); + outputs[MIXER_PWM1] = pwm_clamp(pwm1); + outputs[MIXER_PWM2] = pwm_clamp(pwm2); + outputs[MIXER_PWM3] = pwm_clamp(pwm3); +} + +static void reset_mixer(void *state) {} + +static const char* const in_names[4] = {"Throttle", "Pitch", "Roll", "Yaw"}; +static const char* const out_names[4] = {"PWM 0", "PWM 1", "PWM 2", "PWM 3"}; +static const char* const param_names[0] = {}; +const struct graph_node_type node_mixer_type = { + .input_names = in_names, + .output_names = out_names, + .param_names = param_names, + .n_inputs = 4, + .n_outputs = 4, + .n_params = 0, + .execute = mixer_computation, + .reset = reset_mixer +}; + +int graph_add_node_mixer(struct computation_graph *graph, const char* name) { + return graph_add_node(graph, name, &node_mixer_type, NULL); +} diff --git a/quad/sw/modular_quad_pid/src/graph_blocks/node_mixer.h b/quad/sw/modular_quad_pid/src/graph_blocks/node_mixer.h new file mode 100644 index 0000000000000000000000000000000000000000..1d58cf54d0531725b8ad2c99195dd23fecbe713f --- /dev/null +++ b/quad/sw/modular_quad_pid/src/graph_blocks/node_mixer.h @@ -0,0 +1,23 @@ +#ifndef __NODE_MIXER_H__ +#define __NODE_MIXER_H__ +#include "../computation_graph.h" + +int graph_add_node_mixer(struct computation_graph *graph, const char* name); + +extern const struct graph_node_type node_mixer_type; + +enum graph_node_mixer_inputs { + MIXER_THROTTLE, + MIXER_PITCH, + MIXER_ROLL, + MIXER_YAW, +}; + +enum graph_node_mixer_outputs { + MIXER_PWM0, + MIXER_PWM1, + MIXER_PWM2, + MIXER_PWM3, +}; + +#endif // __NODE_MIXER_H__ diff --git a/quad/sw/modular_quad_pid/src/graph_blocks/node_pid.c b/quad/sw/modular_quad_pid/src/graph_blocks/node_pid.c new file mode 100644 index 0000000000000000000000000000000000000000..acf77b5b4051c446067dfaea395a05cf89d1f916 --- /dev/null +++ b/quad/sw/modular_quad_pid/src/graph_blocks/node_pid.c @@ -0,0 +1,93 @@ +#include "node_pid.h" +#include <stdlib.h> +#include <math.h> + +static double FLT_EPSILON = 0.0001; + + +struct pid_node_state { + double prev_error; // Previous error + double acc_error; // Accumulated error +}; + +// The generic PID diagram. This function takes in pid inputs (CUR_POINT and SETOINT) and calculates the output "pid_correction" +// part based on those parameters. +// +// + --- error ------------------ P + --- ---------------------------- +// setpoint ---> / sum \ --------->| Kp * error |--------------->/ sum \ -------->| output: "pid_correction" | +// \ / | ------------------ \ / ---------------------------- +// --- | --- || +// - ^ | + ^ ^ + || +// | | ------------------------------- | | ------- \/------------ +// | |----->| Ki * accumulated error * dt |----+ | | | +// | | ------------------------------- I | | SYSTEM | +// | | | | | +// | | | --------||------------ +// | | | || +// | | ---------------------------------- | || +// | |----->| Kd * (error - last error) / dt |----+ || +// | ---------------------------------- D || +// | || +// | -----------\/----------- +// |____________________________________________________________| Sensor measurements: | +// | "current point" | +// ------------------------ +// +static void pid_computation(void *state, const double* params, const double *inputs, double *outputs) { + struct pid_node_state* pid_state = (struct pid_node_state*)state; + + double P = 0.0, I = 0.0, D = 0.0; + + // calculate the current error + double error = inputs[PID_SETPOINT] - inputs[PID_CUR_POINT]; + + // Accumulate the error (if Ki is less than epsilon, rougly 0, + // then reset the accumulated error for safety) + if (fabs(params[PID_KI]) <= FLT_EPSILON) { + pid_state->acc_error = 0; + } else { + pid_state->acc_error += error; + } + + double change_in_error = error - pid_state->prev_error; + + // Compute each term's contribution + P = params[PID_KP] * error; + I = params[PID_KI] * pid_state->acc_error * inputs[PID_DT]; + D = params[PID_KD] * (change_in_error / inputs[PID_DT]); + + pid_state->prev_error = error; // Store the current error into the state + + outputs[PID_CORRECTION] = P + I + D; // Store the computed correction +} + +// This function sets the accumulated error and previous error to 0 +// to prevent previous errors from affecting output after a reset +static void reset_pid(void *state) { + struct pid_node_state* pid_state = (struct pid_node_state*)state; + pid_state->acc_error = 0; + pid_state->prev_error = 0; +} + + +static const char* const in_names[3] = {"Cur point", "Setpoint", "dt"}; +static const char* const out_names[1] = {"Correction"}; +static const char* const param_names[3] = {"Kp", "Ki", "Kd"}; +const struct graph_node_type node_pid_type = { + .input_names = in_names, + .output_names = out_names, + .param_names = param_names, + .n_inputs = 3, + .n_outputs = 1, + .n_params = 3, + .execute = pid_computation, + .reset = reset_pid +}; + +int graph_add_node_pid(struct computation_graph *graph, const char* name) { + struct pid_node_state* node_state = malloc(sizeof(struct pid_node_state)); + if (sizeof(struct pid_node_state) && !node_state) { + return -1; // malloc failed + } + return graph_add_node(graph, name, &node_pid_type, node_state); +} diff --git a/quad/sw/modular_quad_pid/src/graph_blocks/node_pid.h b/quad/sw/modular_quad_pid/src/graph_blocks/node_pid.h new file mode 100644 index 0000000000000000000000000000000000000000..75ded4af02edbc2cacbc68035f5a0970d65e38dc --- /dev/null +++ b/quad/sw/modular_quad_pid/src/graph_blocks/node_pid.h @@ -0,0 +1,25 @@ +#ifndef __NODE_PID_H__ +#define __NODE_PID_H__ +#include "../computation_graph.h" + +int graph_add_node_pid(struct computation_graph *graph, const char* name); + +extern const struct graph_node_type node_pid_type; + +enum graph_node_pid_inputs { + PID_CUR_POINT, // Current value of the system + PID_SETPOINT, // Desired value of the system + PID_DT // sample period +}; + +enum graph_node_pid_outputs { + PID_CORRECTION // Correction factor computed by the PID +}; + +enum graph_node_pid_params { + PID_KP, // Proportional constant + PID_KI, // Integral constant + PID_KD // Derivative constant +}; + +#endif // __NODE_PID_H__ diff --git a/quad/sw/modular_quad_pid/src/initialize_components.c b/quad/sw/modular_quad_pid/src/initialize_components.c index 742270a8e476ac8a203a8fff7f6632126cce534a..9c79db823252d961b57acd621fb7a09d662f1d2d 100644 --- a/quad/sw/modular_quad_pid/src/initialize_components.c +++ b/quad/sw/modular_quad_pid/src/initialize_components.c @@ -7,6 +7,7 @@ #include "initialize_components.h" #include "communication.h" +#include "sensor.h" //#define BENCH_TEST @@ -45,6 +46,9 @@ int initializeAllComponents(user_input_t * user_input_struct, log_t * log_struct // Initialize the controller control_algorithm_init(parameter_struct); + // Initialize the logging + initialize_logging(log_struct, parameter_struct); + // Xilinx given initialization init_platform(); diff --git a/quad/sw/modular_quad_pid/src/log_data.c b/quad/sw/modular_quad_pid/src/log_data.c index f49808bfe41b08122194e8e41055bb6a7a19733e..b01698b3c3635d1abf9bee7ef4af4c426de7b6ee 100644 --- a/quad/sw/modular_quad_pid/src/log_data.c +++ b/quad/sw/modular_quad_pid/src/log_data.c @@ -5,68 +5,108 @@ * Author: ucart */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "PID.h" +#include "type_def.h" +#include "uart.h" +#include "sleep.h" #include "log_data.h" #include "communication.h" +#include "computation_graph.h" +#include "graph_blocks/node_pid.h" +#include "graph_blocks/node_constant.h" +#include "graph_blocks/node_mixer.h" // Current index of the log array int arrayIndex = 0; // Size of the array int arraySize = LOG_STARTING_SIZE; -int resized = 0; -// The number of times we resized the array -int resizeCount = 0; +struct graph_tuple { // Tuple for + int block_id; + int sub_id; +}; -// Pointer to point to the array with all the logging information -// for now its not dynamic -log_t logArray[LOG_STARTING_SIZE * 3];// up to 60 seconds of log +struct graph_tuple log_outputs[MAX_LOG_NUM]; +struct graph_tuple log_params[MAX_LOG_NUM]; +size_t n_outputs; +size_t n_params; -int log_data(log_t* log_struct) -{ - updateLog(*log_struct); - return 0; +float* logArray; +int row_size; + +void addOutputToLog(log_t* log_struct, int controller_id, int output_id) { + if (n_outputs < MAX_LOG_NUM) { + log_outputs[n_outputs].block_id = controller_id; + log_outputs[n_outputs].sub_id = output_id; + n_outputs++; + } +} +void addParamToLog(log_t* log_struct, int controller_id, int param_id) { + if (n_params < MAX_LOG_NUM) { + log_params[n_params].block_id = controller_id; + log_params[n_params].sub_id = param_id; + n_params++; + } +} + +void initialize_logging(log_t* log_struct, parameter_t* ps) { + addOutputToLog(log_struct, ps->alt_pid, PID_CORRECTION); + addOutputToLog(log_struct, ps->x_pos_pid, PID_CORRECTION); + addOutputToLog(log_struct, ps->y_pos_pid, PID_CORRECTION); + addOutputToLog(log_struct, ps->pitch_pid, PID_CORRECTION); + addOutputToLog(log_struct, ps->roll_pid, PID_CORRECTION); + addOutputToLog(log_struct, ps->yaw_pid, PID_CORRECTION); + addOutputToLog(log_struct, ps->pitch_r_pid, PID_CORRECTION); + addOutputToLog(log_struct, ps->roll_r_pid, PID_CORRECTION); + addOutputToLog(log_struct, ps->yaw_r_pid, PID_CORRECTION); + addOutputToLog(log_struct, ps->vrpn_x, CONST_VAL); + addOutputToLog(log_struct, ps->vrpn_y, CONST_VAL); + addOutputToLog(log_struct, ps->vrpn_alt, CONST_VAL); + addOutputToLog(log_struct, ps->vrpn_pitch, CONST_VAL); + addOutputToLog(log_struct, ps->vrpn_roll, CONST_VAL); + addOutputToLog(log_struct, ps->x_set, CONST_VAL); + addOutputToLog(log_struct, ps->y_set, CONST_VAL); + addOutputToLog(log_struct, ps->alt_set, CONST_VAL); + addOutputToLog(log_struct, ps->mixer, MIXER_PWM0); + addOutputToLog(log_struct, ps->mixer, MIXER_PWM1); + addOutputToLog(log_struct, ps->mixer, MIXER_PWM2); + addOutputToLog(log_struct, ps->mixer, MIXER_PWM3); + + // TODO: Make this not stupid. Adding 6 for IMU and 1 for timestamp + row_size = n_outputs + n_params + 6 + 1; + logArray = malloc(sizeof(float) * row_size * LOG_STARTING_SIZE); } -/** - * Fills up an xbox hueg amount of memory with log data - */ -void updateLog(log_t log_struct){ - // If the first iteration, allocate enough memory for "arraySize" elements of logging -// if(logArray == NULL){ -// // size in memory is 1,720,320 bytes (1.64 megabytes) because logging struct is 420 bytes each -// // up to 20 seconds of log before resizing -// logArray = malloc(LOG_STARTING_SIZE * sizeof(log_t)); -// uart0_sendStr("initialized log array.\n"); -// sleep(1); -// } - - // semi dynamic log -// if((arrayIndex >= arraySize - 1) && (!resized)){ -// realloc(logArray, LOG_STARTING_SIZE * 3 * sizeof(log_t)); // up to 60 seconds of log -// resized = 1; -// arraySize = LOG_STARTING_SIZE * 3; -// uart0_sendStr("resized log array.\n"); -// sleep(1); -// } +int log_data(log_t* log_struct, parameter_t* ps) +{ if(arrayIndex >= arraySize - 1) { - return; + return 1; + } + float* thisRow = &logArray[arrayIndex * row_size * sizeof(float)]; + int offset = 0; + thisRow[offset++] = log_struct->time_stamp; + thisRow[offset++] = log_struct->gam.accel_x; + thisRow[offset++] = log_struct->gam.accel_y; + thisRow[offset++] = log_struct->gam.accel_z; + thisRow[offset++] = log_struct->gam.gyro_xVel_p; + thisRow[offset++] = log_struct->gam.gyro_yVel_q; + thisRow[offset++] = log_struct->gam.gyro_zVel_r; + + int i; + for (i = 0; i < n_params; i++) { + thisRow[offset++] = graph_get_param_val(ps->graph, log_params[i].block_id, log_params[i].sub_id); + } + for (i = 0; i < n_outputs; i++) { + thisRow[offset++] = graph_get_output(ps->graph, log_outputs[i].block_id, log_outputs[i].sub_id); } - // Add log to the array - logArray[arrayIndex++] = log_struct; - - // If the index is too big, reallocate memory to double the size as before -// if(arrayIndex == arraySize){ -// arraySize *= 2; -// logArray = (log_t *) realloc(logArray, arraySize * sizeof(log_t)); -// ++resizeCount; -// } -// else if(arrayIndex > arraySize){ -// // Something fishy has occured -// xil_printf("Array index is out of bounds. This shouldn't happen but somehow you did the impossible\n\r"); -// } + arrayIndex++; + return 0; } @@ -76,143 +116,54 @@ void updateLog(log_t log_struct){ * TODO: This should probably be transmitting in binary instead of ascii */ -void printLogging(){ +void printLogging(log_t* log_struct, parameter_t* ps){ int i;//, j; - char buf[2304] = {}; - char comments[256] = {}; - char header[1024] = {}; - char units [1024] = {}; - - sprintf(comments, "# MicroCART On-board Quad Log\r\n# Sample size: %d\r\n", arrayIndex); - sprintf(header, "%%Time\t" "LoopPeriod\t" - - //current points (measurements) - "X_Current_Position\t" "Y_Current_Position\t" "Z_Current_Position\t" - "Cam_Meas_Roll\tCam_Meas_Pitch\tCam_Meas_Yaw\t" - "Quad_Meas_Roll\tQuad_Meas_Pitch\t" - "roll_velocity\tpitch_velocity\tyaw_velocity\t" - - //setpoints - "X_setpoint\t" "Y_setpoint\t" "Z_setpoint\t" - "Roll_setpoint\tPitch_setpoint\tYaw_setpoint\t" - "Roll_vel_setpoint\tPitch_vel_setpoint\tYaw_vel_setpoint\t" - - //corrections - "PID_x\t" - "PID_y\t" - "PID_z\t" - "PID_roll\t" - "PID_pitch\t" - "PID_yaw\t" - "PID_roll_vel\t" - "PID_pitch_vel\t" - "PID_yaw_vel\t" - - //trims - "Roll_trim\tPitch_trim\tYaw_trim\tThrottle_trim\t" - - //motor commands - "Motor_0\tMotor_1\tMotor_2\tMotor_3\n" - - ); - - - sprintf(units, "&sec\tsec\t" - - //current points - "meters\tmeters\tmeters\t" - "radians\tradians\tradians\t" - "radians\tradians\t" - "radians//sec\tradians//sec\tradians//sec\t" - - //setpoints - "meters\tmeters\tmeters\t" - "radians\tradians\tradians\t" - "radians//sec\tradians//sec\tradians//sec\t" - - //corrections - "radians\tradians\tradians\t" - "radians//sec\tradians//sec\tradians//sec\t" - "none\tnone\tnone\t" - - //trims - "none\tnone\tnone\tnone\t" - - //motors - "none\tnone\tnone\tnone\n" + char buf[2048] = {}; + buf[0] = 0; // Mark buffer as size 0 + char header1[256] = {}; + char header2[1024] = {}; + + sprintf(header1, "time,accel_x,accel_y,accel_z,gyro_x,gyro_y,gyro_z"); + + int end = 0; + // Print all the recorded block parameters + for (i = 0; i < n_params; i++) { + const char* block_name = ps->graph->nodes[log_params[i].block_id].name; + const char* output_name = ps->graph->nodes[log_params[i].block_id].type->param_names[log_params[i].sub_id]; + end += sprintf(&header2[end], ",%s-%s", block_name, output_name); + } + // Print all the recorded block outputs + for (i = 0; i < n_outputs; i++) { + const char* block_name = ps->graph->nodes[log_outputs[i].block_id].name; + const char* param_name = ps->graph->nodes[log_outputs[i].block_id].type->output_names[log_outputs[i].sub_id]; + end += sprintf(&header2[end], ",%s-%s", block_name, param_name); + } + end += sprintf(&header2[end], "\n"); - ); - //strcat(buf,comments); - strcat(buf,header); - strcat(buf,units); + strcat(buf,header1); + strcat(buf,header2); - send_data(LOG_ID, 0, buf, strlen(buf) + 1); - //uart0_sendBytes(buf, strlen(buf)); - //usleep(100000); + send_data(LOG_ID, 0, buf, strlen(buf)); /*************************/ /* print & send log data */ for(i = 0; i < arrayIndex; i++){ - char* logLine = format(logArray[i]); - send_data(LOG_ID, 0, logLine, strlen(logLine) + 1); - free(logLine); + int size = format_log(i, log_struct, buf); + send_data(LOG_ID, 0, buf, size); } } +int format_log(int idx, log_t* log_struct, char* buf) { + int i; + int end = 0; -char* format(log_t log){ - char *retString = malloc(4096*2); - - sprintf(retString, "%.3f\t%.4f\t" //Time and TimeSlice - - // current points - "%.3f\t%.3f\t%.3f\t" - "%.3f\t%.3f\t%.3f\t" - "%.3f\t%.3f\t" - "%.3f\t%.3f\t%.3f\t" - - //setpoints - "%.3f\t%.3f\t%.3f\t" - "%.3f\t%.3f\t%.3f\t" - "%.3f\t%.3f\t%.3f\t" - - //corrections - "%.3f\t%.3f\t%.3f\t" - "%.3f\t%.3f\t%.3f\t" - "%.3f\t%.3f\t%.3f\t" + float* row = &logArray[idx * row_size * sizeof(float)];\ - //trims - "%.2f\t%.2f\t%.2f\t%.2f\t" - - //motors - "%d\t%d\t%d\t%d\n" - - ,log.time_stamp, log.time_slice, - - // current points - log.local_x_PID.current_point,log.local_y_PID.current_point, log.altitude_PID.current_point, - log.currentQuadPosition.roll, log.currentQuadPosition.pitch, log.currentQuadPosition.yaw, - log.roll_angle_filtered, log.pitch_angle_filtered, - log.phi_dot, log.theta_dot, log.psi_dot, - - //setpoints - log.local_x_PID.setpoint, log.local_y_PID.setpoint, log.altitude_PID.setpoint, - log.angle_roll_PID.setpoint, log.angle_pitch_PID.setpoint, log.angle_yaw_PID.setpoint, - log.ang_vel_roll_PID.setpoint, log.ang_vel_pitch_PID.setpoint, log.ang_vel_pitch_PID.setpoint, - - //corrections - log.local_x_PID_values.pid_correction, log.local_y_PID_values.pid_correction, log.altitude_PID_values.pid_correction, - log.angle_roll_PID_values.pid_correction, log.angle_pitch_PID_values.pid_correction, log.angle_yaw_PID_values.pid_correction, - log.ang_vel_roll_PID_values.pid_correction, log.ang_vel_pitch_PID_values.pid_correction, log.ang_vel_yaw_PID_values.pid_correction, - - //trims - log.trims.roll, log.trims.pitch, log.trims.yaw, log.trims.throttle, - - //motors - log.motors[0], log.motors[1], log.motors[2], log.motors[3] - ); - - - return retString; + end += sprintf(&buf[end], "%f", row[0]); + for (i = 1; i < row_size; i++) { + end += sprintf(&buf[end], ",%f", row[i]); + } + end += sprintf(&buf[end], "\n"); + return end; } diff --git a/quad/sw/modular_quad_pid/src/log_data.h b/quad/sw/modular_quad_pid/src/log_data.h index 994d043ba18abd76c73e8e95ba7e93f514c941e2..bb3622c931f7b56420175ea872420a39c7e904d7 100644 --- a/quad/sw/modular_quad_pid/src/log_data.h +++ b/quad/sw/modular_quad_pid/src/log_data.h @@ -7,16 +7,23 @@ #ifndef LOG_DATA_H_ #define LOG_DATA_H_ - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include "PID.h" #include "type_def.h" -#include "uart.h" -#include "sleep.h" -#define LOG_STARTING_SIZE 4096 //262144 // 2^18 32768 2^15 +#define LOG_STARTING_SIZE 8192 //262144 // 2^18 32768 2^15 +// Maximum number of block outputs you can record, and maximum number of block parameters to record +#define MAX_LOG_NUM 50 + +void initialize_logging(log_t* log_struct, parameter_t* ps); + +/** + * Adds the given block output to the data to be logged + */ +void addOutputToLog(log_t* log_struct, int controller_id, int output_id); + +/** + * Adds the given block parameter to the data to be logged + */ +void addParamToLog(log_t* log_struct, int controller_id, int param_id); /** @@ -30,7 +37,7 @@ * error message * */ - int log_data(log_t* log_struct); + int log_data(log_t* log_struct, parameter_t* ps); /** * Fills up an xbox hueg amount of memory with log data @@ -40,8 +47,11 @@ /** * Prints all the log information. */ - void printLogging(); + void printLogging(log_t* log_struct, parameter_t* ps); - char* format(log_t log); + /** + * Fills the given buffer as ASCII for the recorded index, and returns the length of the string created + */ + int format_log(int idx, log_t* log_struct, char* buf); #endif /* LOG_DATA_H_ */ diff --git a/quad/sw/modular_quad_pid/src/main.c b/quad/sw/modular_quad_pid/src/main.c index d703e348dbdeaf14dae299732c0eff36b892767c..64d82b3a997b1619226249b20c626453398659dd 100644 --- a/quad/sw/modular_quad_pid/src/main.c +++ b/quad/sw/modular_quad_pid/src/main.c @@ -78,10 +78,10 @@ int main() // Run the control algorithm control_algorithm(&(structs.log_struct), &(structs.user_input_struct), &(structs.sensor_struct), &(structs.setpoint_struct), - &(structs.parameter_struct), &(structs.user_defined_struct), &(structs.raw_actuator_struct), &structs); + &(structs.parameter_struct), &(structs.user_defined_struct), &(structs.actuator_command_struct), &structs); // Process the commands going to the actuators - actuator_command_processing(&(structs.log_struct), &(structs.user_input_struct), &(structs.raw_actuator_struct), &(structs.actuator_command_struct)); + //actuator_command_processing(&(structs.log_struct), &(structs.user_input_struct), &(structs.raw_actuator_struct), &(structs.actuator_command_struct)); // send the actuator commands send_actuator_commands(&(structs.log_struct), &(structs.actuator_command_struct)); @@ -95,7 +95,7 @@ int main() if(structs.user_defined_struct.flight_mode == AUTO_FLIGHT_MODE) { // Log the data collected in this loop - log_data(&(structs.log_struct)); + log_data(&(structs.log_struct), &(structs.parameter_struct)); static int loop_counter = 0; loop_counter++; @@ -128,11 +128,9 @@ int main() MIO7_led_off(); - printLogging(); + printLogging(&(structs.log_struct), &(structs.parameter_struct)); flash_MIO_7_led(10, 100); return 0; } - - diff --git a/quad/sw/modular_quad_pid/src/send_actuator_commands.c b/quad/sw/modular_quad_pid/src/send_actuator_commands.c index 7174348923252cf7109fd1d2df8d08bcef673313..3ef6f1a87140fa572d0118c3fc73b3bdf4fb667b 100644 --- a/quad/sw/modular_quad_pid/src/send_actuator_commands.c +++ b/quad/sw/modular_quad_pid/src/send_actuator_commands.c @@ -6,6 +6,7 @@ */ #include "send_actuator_commands.h" +#include "util.h" int send_actuator_commands(log_t* log_struct, actuator_command_t* actuator_command_struct) { @@ -14,11 +15,6 @@ int send_actuator_commands(log_t* log_struct, actuator_command_t* actuator_comma for (i = 0; i < 4; i++) pwm_write_channel(actuator_command_struct->pwms[i], i); - log_struct->motors[0] = actuator_command_struct->pwms[0]; - log_struct->motors[1] = actuator_command_struct->pwms[1]; - log_struct->motors[2] = actuator_command_struct->pwms[2]; - log_struct->motors[3] = actuator_command_struct->pwms[3]; - return 0; } diff --git a/quad/sw/modular_quad_pid/src/sensor_processing.c b/quad/sw/modular_quad_pid/src/sensor_processing.c index 5fd682c3fae6a0bf0039932e24a20dc6eff27120..410b9219f8c905e16ab07fbfa0e41958bdd2ba53 100644 --- a/quad/sw/modular_quad_pid/src/sensor_processing.c +++ b/quad/sw/modular_quad_pid/src/sensor_processing.c @@ -5,6 +5,11 @@ * Author: ucart */ +#include <stdio.h> +#include "log_data.h" +#include "sensor.h" +#include "conversion.h" +#include "quadposition.h" #include "sensor_processing.h" #include "timer.h" #include <math.h> @@ -91,14 +96,6 @@ int sensor_processing(log_t* log_struct, user_input_t *user_input_struct, raw_se // uart0_sendStr(dMsg); // loop_counter = 0; // } - - //logging - log_struct->currentQuadPosition = sensor_struct->currentQuadPosition; - log_struct->roll_angle_filtered = sensor_struct->roll_angle_filtered; - log_struct->pitch_angle_filtered = sensor_struct->pitch_angle_filtered; - log_struct->phi_dot = sensor_struct->phi_dot; - log_struct->theta_dot = sensor_struct->theta_dot; - log_struct->psi_dot = sensor_struct->psi_dot; return 0; } diff --git a/quad/sw/modular_quad_pid/src/sensor_processing.h b/quad/sw/modular_quad_pid/src/sensor_processing.h index cf3fd122b33168782cccbb7dd1edc012ca3c2107..04b5efebe48ab20bdecfcfaab11e1ffe2f63a540 100644 --- a/quad/sw/modular_quad_pid/src/sensor_processing.h +++ b/quad/sw/modular_quad_pid/src/sensor_processing.h @@ -7,12 +7,7 @@ #ifndef SENSOR_PROCESSING_H_ #define SENSOR_PROCESSING_H_ - -#include <stdio.h> -#include "log_data.h" -#include "sensor.h" -#include "conversion.h" -#include "quadposition.h" +#include "type_def.h" /* * Aero channel declaration */ diff --git a/quad/sw/modular_quad_pid/src/type_def.h b/quad/sw/modular_quad_pid/src/type_def.h index c6f89b496581f5241b2bfa392d50f1fbda319fd5..2157f3b0319920c779c31c40e6c9db4bea68341e 100644 --- a/quad/sw/modular_quad_pid/src/type_def.h +++ b/quad/sw/modular_quad_pid/src/type_def.h @@ -10,6 +10,7 @@ #include <stdint.h> #include "commands.h" +#include "computation_graph.h" /** * @brief @@ -168,6 +169,8 @@ typedef struct log_t { int packetId; gam_t gam; // Raw and calculated gyro, accel, and mag values are all in gam_t + + /* float phi_dot, theta_dot, psi_dot; // gimbal equation values quadPosition_t currentQuadPosition; @@ -193,7 +196,7 @@ typedef struct log_t { quadTrims_t trims; int motors[4]; - + */ } log_t; /** @@ -287,7 +290,51 @@ typedef struct setpoint_t { * */ typedef struct parameter_t { - PID_t pid_controllers[MAX_CONTROLLER_ID]; + struct computation_graph* graph; + // PID blocks + int roll_pid; + int pitch_pid; + int yaw_pid; + int roll_r_pid; + int pitch_r_pid; + int yaw_r_pid; + int x_pos_pid; + int y_pos_pid; + int alt_pid; + // Sensor blocks + int cur_pitch; + int cur_roll; + int cur_yaw; + int theta_dot; + int phi_dot; + int psi_dot; + // VRPN blocks + int vrpn_x; + int vrpn_y; + int vrpn_alt; + int vrpn_pitch, vrpn_roll; + // RC blocks + int rc_pitch; + int rc_roll; + int rc_yaw; + int rc_throttle; + // Desired positions + int x_set; + int y_set; + int alt_set; + int yaw_set; + // Clamps + int clamp_d_pwmP; + int clamp_d_pwmR; + int clamp_d_pwmY; + // Loop times + int angle_time; + int pos_time; + // Signal mixer + int mixer; + // "trim" for autonomous + int throttle_trim; + int throttle_trim_add; } parameter_t; /** diff --git a/quad/sw/modular_quad_pid/src/user_input.c b/quad/sw/modular_quad_pid/src/user_input.c index bc0c4c5b7047afa4cdecf6093e48a6b977bf9a15..b3b4331e200aad0dfc88c87684e7d28b06def909 100644 --- a/quad/sw/modular_quad_pid/src/user_input.c +++ b/quad/sw/modular_quad_pid/src/user_input.c @@ -7,17 +7,13 @@ #include "user_input.h" #include "uart.h" +#include "controllers.h" int get_user_input(log_t* log_struct, user_input_t* user_input_struct) { // Read in values from RC Receiver read_rec_all(user_input_struct->rc_commands); - log_struct->commands.pitch = user_input_struct->rc_commands[PITCH]; - log_struct->commands.roll = user_input_struct->rc_commands[ROLL]; - log_struct->commands.throttle = user_input_struct->rc_commands[THROTTLE]; - log_struct->commands.yaw = user_input_struct->rc_commands[YAW]; - //create setpoints for manual flight // currently in units of radians user_input_struct->yaw_manual_setpoint = convert_from_receiver_cmd(user_input_struct->rc_commands[YAW], YAW_MAX, YAW_CENTER, YAW_MIN, YAW_RAD_TARGET, -(YAW_RAD_TARGET)); diff --git a/quad/sw/modular_quad_pid/src/util.c b/quad/sw/modular_quad_pid/src/util.c index c7dd33a1ca988a454de416311fca18fcfbc5fe1c..be07d38187f621dc63405b031eb5ff3131d81a5b 100644 --- a/quad/sw/modular_quad_pid/src/util.c +++ b/quad/sw/modular_quad_pid/src/util.c @@ -7,6 +7,18 @@ #include "util.h" +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <math.h> +#include <xgpiops.h> +#include "PID.h" +#include "log_data.h" +#include <sleep.h> +#include "controllers.h" +#include "xparameters.h" +#include "uart.h" + extern int motor0_bias, motor1_bias, motor2_bias, motor3_bias; //Global variable representing the current pulseW int pulseW = pulse_throttle_low; diff --git a/quad/sw/modular_quad_pid/src/util.h b/quad/sw/modular_quad_pid/src/util.h index 285db6085f1804ebca654ec969958adf3abd9955..7c864f4d6ef165749649293280f4630805b89045 100644 --- a/quad/sw/modular_quad_pid/src/util.h +++ b/quad/sw/modular_quad_pid/src/util.h @@ -7,17 +7,7 @@ #ifndef _UTIL_H #define _UTIL_H -#include <stdlib.h> -#include <stdio.h> -#include <string.h> -#include <math.h> -#include <xgpiops.h> -#include "PID.h" -#include "log_data.h" -#include <sleep.h> -#include "controllers.h" -#include "xparameters.h" -#include "uart.h" +#include "type_def.h" #define clock_rate 100000000 diff --git a/wifi_bridge/test_scripts/serial-tcp.py b/wifi_bridge/test_scripts/serial-tcp.py new file mode 100644 index 0000000000000000000000000000000000000000..9e81d02bdc521cadd204e472a7fb5737320975c5 --- /dev/null +++ b/wifi_bridge/test_scripts/serial-tcp.py @@ -0,0 +1,62 @@ +import socket +import serial + +TCP_IP = "192.168.1.1" +TCP_PORT = 8080 + +msg_size = 1024 +message = bytes(i % 256 for i in range(msg_size)) +dropped = True + +ser = serial.Serial('COM6', 921600, timeout=0.01) +ser.reset_input_buffer() +if ser.in_waiting: + print("that doesn't work") + +while True: + if dropped: + attempts = 0 + while attempts < 5: + print("Trying to connect") + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(2) + try: + sock.connect((TCP_IP, TCP_PORT)) + dropped = False + break + except: + attempts += 1 + if dropped: + print("Failed to connect") + break + print("connected") + + try: + print("Sending {} bytes".format(len(message))) + ser.write(message) + except Exception as e: + print("Failed to send all data") + continue + + received = [] + while len(received) < msg_size: + try: + just_received = sock.recv(msg_size - len(received)) + except Exception as e: + print("Exception when receiving: ", e) + break + if len(just_received) == 0: + print("Your socket broke") + break + received.extend(just_received) + + if len(received) != msg_size: + print("\tError: Received {} bytes".format(len(received))) + elif bytes(received) != message: + print("\tError: Received data does not match") + else: + print("\tYou're a winner!") + + debug_msg = ser.read(4096) + if len(debug_msg) != 0: + print(debug_msg.decode()) diff --git a/wifi_bridge/test_scripts/serial-tcp_nowait.py b/wifi_bridge/test_scripts/serial-tcp_nowait.py new file mode 100644 index 0000000000000000000000000000000000000000..d2279c3998790b5fcb8121e585c9d514633dd3ef --- /dev/null +++ b/wifi_bridge/test_scripts/serial-tcp_nowait.py @@ -0,0 +1,74 @@ +import socket +import serial +import time + +TCP_IP = "192.168.1.1" +TCP_PORT = 8080 + +msg_size = 500 +print_interval = 100 +message = bytes(i % 256 for i in range(msg_size)) +dropped = True +loop_time = 0.005 + +ser = serial.Serial('COM6', 921600, timeout=0.01) +ser.reset_input_buffer() + +sent_cnt = 0 +recvd_cnt = 0 +outer_start_time = time.perf_counter() +received = [] +while True: + if dropped: + attempts = 0 + while attempts < 5: + print("Trying to connect") + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(2) + try: + sock.connect((TCP_IP, TCP_PORT)) + dropped = False + break + except: + attempts += 1 + if dropped: + print("Failed to connect") + break + print("connected") + + start_time = time.perf_counter() + try: + ser.write(message) + sent_cnt += 1 + except Exception as e: + print("Failed to send all data") + continue + + # while len(received) < msg_size: + try: + amt_in_air = (sent_cnt - recvd_cnt) * msg_size - len(received) + just_received = sock.recv(1024) + received.extend(just_received) + if len(just_received) == 0: + print("Your socket broke") + break + except Exception as e: + print("Exception when receiving: ", e) + break + if len(received) >= msg_size: + recvd_packet = received[:msg_size] + received = received[msg_size:] + recvd_cnt += 1 + if bytes(recvd_packet) != message: + print("Received data does not match") + if sent_cnt % print_interval == 0: + print("Sent {}, received {}".format(sent_cnt, recvd_cnt)) + print("Average send time was {}".format((time.perf_counter() - outer_start_time)/print_interval)) + outer_start_time = time.perf_counter() + + while time.perf_counter() - start_time <= loop_time: + pass + + #debug_msg = ser.read(4096) + #if len(debug_msg) != 0: + # print(debug_msg.decode()) diff --git a/wifi_bridge/test_scripts/tcp-serial.py b/wifi_bridge/test_scripts/tcp-serial.py new file mode 100644 index 0000000000000000000000000000000000000000..8c2713096d1e56dd4f56cd509e05be132db7c338 --- /dev/null +++ b/wifi_bridge/test_scripts/tcp-serial.py @@ -0,0 +1,46 @@ +import socket +import serial + +TCP_IP = "192.168.1.1" +TCP_PORT = 8080 + +msg_size = 1024*100 +message = bytes(i % 256 for i in range(msg_size)) +dropped = True + +ser = serial.Serial('COM6', 921600, timeout=5) +ser.reset_input_buffer() +if ser.in_waiting: + print("that doesn't work") + +while True: + if dropped: + attempts = 0 + while attempts < 5: + print("Trying to connect") + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(2) + try: + sock.connect((TCP_IP, TCP_PORT)) + dropped = False + break + except: + attempts += 1 + if dropped: + print("Failed to connect") + break + print("connected") + + try: + print("Sending {} bytes".format(len(message))) + sock.sendall(message) + except Exception as e: + print("Failed to send all data") + continue + received = ser.read(msg_size) + if len(received) != msg_size: + print("\tError: Received {} bytes".format(len(received))) + elif received != message: + print("\tError: Received data does not match") + else: + print("\tYou're a winner!") diff --git a/wifi_bridge/test_scripts/tcp_timer.py b/wifi_bridge/test_scripts/tcp_timer.py new file mode 100644 index 0000000000000000000000000000000000000000..78664a980b15574af94a883fd654dfd71d85696c --- /dev/null +++ b/wifi_bridge/test_scripts/tcp_timer.py @@ -0,0 +1,65 @@ +import socket +import time +import csv + +TCP_IP = "192.168.1.1" +# UDP_IP = "127.0.0.1" +TCP_PORT = 8080 + +# sock.bind(('', UDP_PORT)) + +message = bytes(range(36)) +times_full = [] +times_network = [] +times = [0.0]*100 +dropped = True +response = bytes("initial", 'ASCII') +addr = "initial" +recvd_data = [] +for i in range(100): + if dropped: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + sock.settimeout(2) + sock.connect((TCP_IP, TCP_PORT)) + dropped = False + send_msg = list(message) + send_msg[0] = i + send_msg = bytes(send_msg) + + send_time = time.perf_counter() + times[i] = send_time + sock.send(send_msg) + try: + response = sock.recv(1024) + recvd_data.extend(response) + except: + print("timed out") + dropped = True + if len(recvd_data) >= 36: + end_time = time.perf_counter() + response = bytes(recvd_data[0:36]) + recvd_data = recvd_data[36:] + msg_id = int(response[0]) + latency = end_time - times[msg_id] + # serial_time = int.from_bytes(response[0:4], byteorder='big') / 1000 + serial_time = 0 + # times_full.append(1000 * (end_time - send_time) - 0) + times_full.append(1000 * latency) + times_network.append(1000 * (end_time - send_time) - serial_time) + print("received " + str(response) + " in " + str(times_full[-1]) + " from " + str(addr)) + while time.perf_counter() - send_time < 0.01: + pass + +# with open("tcp_dist.csv", 'w', newline='') as f: +# writer = csv.writer(f) +# for time in times_network: +# time = time + 2.6 +# writer.writerow([time]) + +for time in [times_full, times_network]: + print("lowest: " + str(min(time))) + print("highest: " + str(max(time))) + print("median: " + str(sorted(time)[int(len(time) / 2)])) + print("average; " + str(sum(time) / len(time))) +