mirror of
https://github.com/caperren/school_archives.git
synced 2025-11-09 21:51:15 +00:00
191 lines
7.2 KiB
Python
191 lines
7.2 KiB
Python
#!/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()
|
|
|