mirror of
https://github.com/caperren/school_archives.git
synced 2025-11-09 13:41:13 +00:00
Added 2016-2017 rover code, and a ROB assignment
This commit is contained in:
190
OSU Robotics Club/Mars Rover 2016-2017/common/codegen.py
Normal file
190
OSU Robotics Club/Mars Rover 2016-2017/common/codegen.py
Normal file
@@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env python3
|
||||
#Generates communications code for the miniboard firmware, based on
|
||||
#a command protocol specification document (see docparse.py) supplied
|
||||
#as the first command-line argument.
|
||||
#
|
||||
#This code generation system has two goals: to allow commands to be easily added
|
||||
#to the spec prior to their implementation, and to ensure that all commands
|
||||
#function consistently. To accomplish this, all command-specific communication
|
||||
#code is generated automatically from the specification document.
|
||||
#For this to be possible, the code that actually carries out each command
|
||||
#must be placed elsewhere.
|
||||
#
|
||||
#Conceptually, commands to the rover read and write registers in its memory.
|
||||
#This model makes separating communication and action easy. The automatically-generated
|
||||
#communication code reads and writes fields in a large global struct (also automatically
|
||||
#generated), with each field corresponding to a command argument.
|
||||
#
|
||||
#The output of this program is a source file and a header file containing the auto-generated
|
||||
#communication code.
|
||||
#The global structure is called DataReal. Due to issues with the volatile qualifier
|
||||
#(the main code needs it, but the ISR doesn't), the main code accesses DataReal through
|
||||
#a pointer called Data.
|
||||
#The parse_packet(uint8_t *buf, uint16_t count) function initiates packet
|
||||
#parsing and dispatches the appropriate reply. buf must contain the command
|
||||
#byte as the first byte in the buffer. count must be the number of bytes in
|
||||
#the packet, not including the start or end bytes, and with escape bytes removed.
|
||||
#
|
||||
#The host program must provide a send_packet(uint8_t *data, uint16_t count) function
|
||||
#which adds start/end bytes and escapes special characters, then sends the packet
|
||||
#over the radio.
|
||||
#The host program must also provide a memcpy() function.
|
||||
#
|
||||
#Additionally, there is an asynchronous command element beyond the simple read/write memory
|
||||
#model. When a command includes a variable-length argument, a <command name>_trigger(uint8_t *data, uint16_t *len)
|
||||
#function will be called when the command is received. This function must be implemented by
|
||||
#the host code. The purpose of this mechanism is to notify the action code when the data
|
||||
#changes. The trigger function must be capable of being run in an interrupt.
|
||||
import sys
|
||||
from docparse import *
|
||||
|
||||
def get_all_args(cmd_list):
|
||||
"""Return a list of tuples (format code, argument name, note comment)."""
|
||||
l = list()
|
||||
for c in cmd_list:
|
||||
for a in c["argument"]:
|
||||
l += [(a[0], a[1], c["notes"])]
|
||||
return l
|
||||
|
||||
def gen_struct_def(cmd_list):
|
||||
s = "struct comm_data_t {\n"
|
||||
for c in get_all_args(cmd_list):
|
||||
dt = format_code_to_cstdint(c[0])
|
||||
if "*" in dt:
|
||||
#Arrays
|
||||
s += "\t" + dt[0:-2] + " " + c[1] + "[%d];"%(2 ** int(dt[4:5])) + " /* " + c[2] + " */\n"
|
||||
else:
|
||||
s += "\t" + dt + " " + c[1] + ";" " /* " + c[2] + " */\n"
|
||||
s += "};\n\n"
|
||||
return s
|
||||
|
||||
def gen_build_str_def():
|
||||
"""Return a declaration for a progmem string containing information about the build."""
|
||||
return ""
|
||||
|
||||
def gen_build_str_dec():
|
||||
"""Return a definition of a PROGMEM string containing information about the build."""
|
||||
#Get name of person building firmware
|
||||
#git config --get-all user.name
|
||||
#Get repo revision
|
||||
#git log | head -1 | cut -d " " -f 2
|
||||
#Get branch
|
||||
#git branch | grep "\*" | cut -d " " -f 2
|
||||
#Get modified status
|
||||
#Date, time, gcc version (__VERSION__)
|
||||
s = "Miniboard Firmware rev "
|
||||
return ""
|
||||
|
||||
|
||||
def gen_header(cmd_list):
|
||||
"""Return a string containing the C header for the communication module."""
|
||||
s = "/* Warning: This file is automatically generated. Do not modify. */\n"
|
||||
s += "#ifndef COMMGEN_H\n"
|
||||
s += "#define COMMGEN_H\n\n"
|
||||
s += "#ifdef __cplusplus\n"
|
||||
s += "extern \"C\" {\n"
|
||||
s += "#endif\n\n"
|
||||
s += "#include <stdint.h>\n\n"
|
||||
s += gen_struct_def(cmd_list)
|
||||
s += "/* To avoid the volatile qualifier being a pain in the ass, the main loop\n"
|
||||
s += " * accesses the DataReal struct through this pointer. */\n"
|
||||
s += "extern volatile struct comm_data_t *Data;\n\n"
|
||||
s += "/* Parse a packet, update the struct, and send a reply. */\n"
|
||||
s += "void parse_packet(uint8_t *buf, uint16_t count);\n\n"
|
||||
for c in cmd_list:
|
||||
s += gen_send_proto(c) + "\n"
|
||||
s + gen_parse_proto(c) + "\n"
|
||||
s += gen_packing_protos()
|
||||
s += gen_build_str_dec()
|
||||
#s += "void send_packet(uint8_t *data, uint16_t count);\n\n"
|
||||
s += "#ifdef __cplusplus\n"
|
||||
s += "}\n"
|
||||
s += "#endif\n\n"
|
||||
s += "#endif\n"
|
||||
return s
|
||||
|
||||
def gen_struct_dec(cmd_list):
|
||||
s = "struct comm_data_t DataReal = {\n"
|
||||
for c in cmd_list:
|
||||
for i,d in zip(list(range(0, len(c["default"]))), c["default"]):
|
||||
s += "\t." + c["argument"][i][1] + " = " + d + ",\n"
|
||||
s += "};\n"
|
||||
s += "volatile struct comm_data_t *Data = &DataReal;\n"
|
||||
return s
|
||||
|
||||
def gen_source(cmd_list):
|
||||
s = "/* Warning: This file is automatically generated. Do not modify. */\n"
|
||||
s += "#include <stdint.h>\n"
|
||||
s += "#include <string.h>\n"
|
||||
s += "#include \"commgen.h\"\n\n"
|
||||
s += "#include \"comm.h\"\n\n"
|
||||
s += gen_struct_dec(cmd_list)
|
||||
for c in cmd_list:
|
||||
s += gen_parse_func(c) + "\n"
|
||||
s += gen_send_func(c, False) + "\n"
|
||||
s += gen_packing_funcs()
|
||||
s += gen_parse_packet_source(cmd_list)
|
||||
s += gen_build_str_def()
|
||||
return s
|
||||
|
||||
def gen_parse_packet_source(cmd_list):
|
||||
#TODO: check for count == 0
|
||||
"""Return a string containing the source code to the
|
||||
parse_packet(uint8_t *buf, uint16_t count)
|
||||
function, which parses a packet, updates values in the global Data structure,
|
||||
and dispatches a reply.
|
||||
The function relies on the following special functions:
|
||||
send_packet(uint8_t *data, uint16_t count) - send the given packet across the radio link.
|
||||
The send_packet() function must add a start and end byte and
|
||||
escape characters where necessary.
|
||||
as well as the send_* and parse_* functions."""
|
||||
s = ""
|
||||
s += "void parse_packet(uint8_t *buf, uint16_t count){\n"
|
||||
s += "\tuint8_t cmd = buf[0];\n"
|
||||
s += "\tswitch(cmd){\n"
|
||||
for c in cmd_list:
|
||||
s += "\t\t/* %s */\n"%(c["name"])
|
||||
s += "\t\tcase 0x%02X: /* (Write form) */\n"%c["code"]
|
||||
s += "\t\t\tparse_%s(buf, "%cannon_name(c["name"])
|
||||
add_trigger = False
|
||||
for a in c["argument"]:
|
||||
if a[0] == "*":
|
||||
s += "DataReal.%s, "%(a[1])
|
||||
add_trigger = True;
|
||||
else:
|
||||
s += "&(DataReal.%s), "%(a[1])
|
||||
s = s[0:-2] + ");\n"
|
||||
s += "\t\t\tbuf[0] = cmd;\n"
|
||||
s += "\t\t\tsend_packet(buf, 1);\n"
|
||||
if add_trigger:
|
||||
s += "\t\t\t%s_trigger();\n"%cannon_name(c["name"])
|
||||
s += "\t\t\tbreak;\n"
|
||||
|
||||
s += "\t\tcase 0x%02X: /* (Read form) */\n"%(c["code"] | 0x80)
|
||||
s += "\t\t\tsend_%s("%cannon_name(c["name"])
|
||||
for a in c["argument"]:
|
||||
s += "DataReal.%s, "%(a[1])
|
||||
s = s[0:-2] + ");\n"
|
||||
s += "\t\t\tbreak;\n"
|
||||
s += "\t\tdefault:\n"
|
||||
s += "\t\t\tbuf[0] = 0;\n"
|
||||
s += "\t\t\tsend_packet(buf, 1);\n"
|
||||
s += "\t\t\tbreak;\n"
|
||||
s += "\t}\n}\n"
|
||||
return s
|
||||
#TODO: writeable stuff
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 4:
|
||||
sys.stderr.write("error: wrong number of arguments. Expected path to spec file, source file, and header file.")
|
||||
with open(sys.argv[1], "r") as f:
|
||||
cmds = extract_table(f.read())
|
||||
with open(sys.argv[3], "w") as f:
|
||||
f.write(gen_header(cmds))
|
||||
with open(sys.argv[2], "w") as f:
|
||||
f.write(gen_source(cmds))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user