Skip to content

Commit

Permalink
Arduino RPC server
Browse files Browse the repository at this point in the history
  • Loading branch information
guberti committed Aug 10, 2021
1 parent 1fc98fe commit 7fad760
Show file tree
Hide file tree
Showing 18 changed files with 416 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ tvm_workspace_t app_workspace;
// Blink code for debugging purposes
void TVMPlatformAbort(tvm_crt_error_t error) {
for (;;) {
#ifdef LED_BUILTIN
digitalWrite(LED_BUILTIN, HIGH);
delay(250);
digitalWrite(LED_BUILTIN, LOW);
Expand All @@ -23,6 +24,7 @@ void TVMPlatformAbort(tvm_crt_error_t error) {
delay(250);
digitalWrite(LED_BUILTIN, LOW);
delay(750);
#endif
}
}

Expand Down Expand Up @@ -71,13 +73,10 @@ tvm_crt_error_t TVMPlatformGenerateRandom(uint8_t* buffer, size_t num_bytes) {
void TVMInitialize() { StackMemoryManager_Init(&app_workspace, g_aot_memory, WORKSPACE_SIZE); }

void TVMExecute(void* input_data, void* output_data) {
void* inputs[1] = {
input_data,
};
void* outputs[1] = {
output_data,
};
int ret_val = tvm_runtime_run(&tvmgen_default_network, inputs, outputs);
int ret_val = tvmgen_default_run_model(input_data, output_data);
if (ret_val != 0) {
TVMPlatformAbort(kTvmErrorPlatformCheckFailure);
}
}

#endif
19 changes: 19 additions & 0 deletions apps/microtvm/arduino/example_project/src/model.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#ifndef IMPLEMENTATION_H_
#define IMPLEMENTATION_H_

#define WORKSPACE_SIZE $workspace_size_bytes

#ifdef __cplusplus
extern "C" {
#endif

void TVMInitialize();

// TODO template these void* values once MLF format has input and output data
void TVMExecute(void* input_data, void* output_data);

#ifdef __cplusplus
} // extern "C"
#endif

#endif // IMPLEMENTATION_H_
14 changes: 1 addition & 13 deletions apps/microtvm/arduino/host_driven/project.ino
Original file line number Diff line number Diff line change
@@ -1,30 +1,26 @@
#include "src/standalone_crt/include/tvm/runtime/crt/microtvm_rpc_server.h"
#include "src/standalone_crt/include/tvm/runtime/crt/logging.h"
#include "src/model.h"
static size_t g_num_bytes_requested = 0;
static size_t g_num_bytes_written = 0;
microtvm_rpc_server_t server;

// Called by TVM to write serial data to the UART.
ssize_t write_serial(void* unused_context, const uint8_t* data, size_t size) {
g_num_bytes_requested += size;
Serial.write(data, size);
return size;
}

void setup() {
server = MicroTVMRpcServerInit(write_serial, NULL);
TVMLogf("microTVM Arduino runtime - running");
Serial.begin(115200);
}

void loop() {
noInterrupts();
int to_read = Serial.available();
uint8_t data[to_read];
size_t bytes_read = Serial.readBytes(data, to_read);
uint8_t* arr_ptr = data;
uint8_t** data_ptr = &arr_ptr;

if (bytes_read > 0) {
size_t bytes_remaining = bytes_read;
while (bytes_remaining > 0) {
Expand All @@ -33,14 +29,6 @@ void loop() {
if (err != kTvmErrorNoError && err != kTvmErrorFramingShortPacket) {
TVMPlatformAbort(err);
}
if (g_num_bytes_written != 0 || g_num_bytes_requested != 0) {
if (g_num_bytes_written != g_num_bytes_requested) {
TVMPlatformAbort((tvm_crt_error_t)0xbeef5);
}
g_num_bytes_written = 0;
g_num_bytes_requested = 0;
}
}
}
interrupts();
}
33 changes: 15 additions & 18 deletions apps/microtvm/arduino/host_driven/src/model.c
Original file line number Diff line number Diff line change
@@ -1,30 +1,22 @@
#ifndef TVM_IMPLEMENTATION_ARDUINO
#define TVM_IMPLEMENTATION_ARDUINO

#include "stdarg.h"

#include "model.h"

#include "Arduino.h"
#include "standalone_crt/include/tvm/runtime/crt/internal/aot_executor/aot_executor.h"
#include "standalone_crt/include/tvm/runtime/crt/stack_allocator.h"

// AOT memory array
static uint8_t g_aot_memory[WORKSPACE_SIZE];
extern tvm_model_t tvmgen_default_network;
tvm_workspace_t app_workspace;
#include "stdarg.h"

// Blink code for debugging purposes
void TVMPlatformAbort(tvm_crt_error_t error) {
for (;;) {
digitalWrite(LED_BUILTIN, HIGH);
delay(250);
digitalWrite(LED_BUILTIN, LOW);
delay(250);
digitalWrite(LED_BUILTIN, HIGH);
delay(250);
digitalWrite(LED_BUILTIN, LOW);
delay(750);
for (int i = 0; i < 4; i++) {
digitalWrite(LED_BUILTIN, HIGH);
delay(250);
digitalWrite(LED_BUILTIN, LOW);
delay(250);
}
delay(1000);
}
}

Expand All @@ -34,11 +26,16 @@ size_t TVMPlatformFormatMessage(char* out_buf, size_t out_buf_size_bytes, const
}

tvm_crt_error_t TVMPlatformMemoryAllocate(size_t num_bytes, DLDevice dev, void** out_ptr) {
return StackMemoryManager_Allocate(&app_workspace, num_bytes, out_ptr);
if (num_bytes == 0) {
num_bytes = sizeof(int);
}
*out_ptr = malloc(num_bytes);
return (*out_ptr == NULL) ? kTvmErrorPlatformNoMemory : kTvmErrorNoError;
}

tvm_crt_error_t TVMPlatformMemoryFree(void* ptr, DLDevice dev) {
return StackMemoryManager_Free(&app_workspace, ptr);
free(ptr);
return kTvmErrorNoError;
}

unsigned long g_utvm_start_time;
Expand Down
2 changes: 1 addition & 1 deletion apps/microtvm/arduino/host_driven/src/model.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#ifndef IMPLEMENTATION_H_
#define IMPLEMENTATION_H_

#define WORKSPACE_SIZE 0
#define WORKSPACE_SIZE 65535

#endif // IMPLEMENTATION_H_
116 changes: 86 additions & 30 deletions apps/microtvm/arduino/template_project/microtvm_api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ class BoardAutodetectFailed(Exception):


BOARD_PROPERTIES = {
"due": {
"package": "arduino",
"architecture": "sam",
"board": "arduino_due_x"
},
# Due to the way the Feather S2 bootloader works, compilation
# behaves fine but uploads cannot be done automatically
"feathers2": {
"package": "esp32",
"architecture": "esp32",
"board": "feathers2",
},
# Spresense only works as of its v2.3.0 sdk
"spresense": {
"package": "SPRESENSE",
"architecture": "spresense",
Expand All @@ -51,12 +64,27 @@ class BoardAutodetectFailed(Exception):
"architecture": "mbed_nano",
"board": "nano33ble",
},
"pybadge": {
"package": "adafruit",
"architecture": "samd",
"board": "adafruit_pybadge_m4",
},
# The Teensy boards are listed here for completeness, but they
# won't work until https://github.com/arduino/arduino-cli/issues/700
# is finished
"teensy40": {
"package": "teensy",
"architecture": "avr",
"board": "teensy40",
},
"teensy41": {
"package": "teensy",
"architecture": "avr",
"board": "teensy41",
},
}

PROJECT_TYPES = [
"template_project",
"host_driven"
]
PROJECT_TYPES = ["example_project", "host_driven"]

PROJECT_OPTIONS = [
server.ProjectOption(
Expand All @@ -67,7 +95,7 @@ class BoardAutodetectFailed(Exception):
server.ProjectOption("arduino_cli_cmd", help="Path to the arduino-cli tool."),
server.ProjectOption("port", help="Port to use for connecting to hardware"),
server.ProjectOption(
"project_type",
"example_project",
help="Type of project to generate.",
choices=tuple(PROJECT_TYPES),
),
Expand All @@ -94,9 +122,14 @@ def server_info_query(self):

def _copy_project_files(self, api_server_dir, project_dir, project_type):
project_types_folder = api_server_dir.parents[0]
shutil.copytree(project_types_folder / project_type / "src", project_dir / "src", dirs_exist_ok=True)
shutil.copytree(
project_types_folder / project_type / "src", project_dir / "src", dirs_exist_ok=True
)
# Arduino requires the .ino file have the same filename as its containing folder
shutil.copy2(project_types_folder / project_type / "project.ino", project_dir / f"{project_dir.stem}.ino")
shutil.copy2(
project_types_folder / project_type / "project.ino",
project_dir / f"{project_dir.stem}.ino",
)

CRT_COPY_ITEMS = ("include", "src")

Expand All @@ -111,27 +144,40 @@ def _copy_standalone_crt(self, source_dir, standalone_crt_dir):
else:
shutil.copy2(src_path, dst_path)

UNUSED_COMPONENTS = [

# Example project is the "minimum viable project",
# and doesn't need a fancy RPC server
EXAMPLE_PROJECT_UNUSED_COMPONENTS = [
"include/dmlc",
"src/support",
"src/runtime/minrpc",
"src/runtime/crt/graph_executor",
"src/runtime/crt/microtvm_rpc_common",
"src/runtime/crt/microtvm_rpc_server",
"src/runtime/crt/tab",
]

def _remove_unused_components(self, source_dir):
for component in self.UNUSED_COMPONENTS:
def _remove_unused_components(self, source_dir, project_type):
unused_components = []
if project_type == "example_project":
unused_components = self.EXAMPLE_PROJECT_UNUSED_COMPONENTS

for component in unused_components:
shutil.rmtree(source_dir / "standalone_crt" / component)

def _disassemble_mlf(self, mlf_tar_path, source_dir):
with tempfile.TemporaryDirectory() as mlf_unpacking_dir:
with tempfile.TemporaryDirectory() as mlf_unpacking_dir_str:
mlf_unpacking_dir = pathlib.Path(mlf_unpacking_dir_str)
with tarfile.open(mlf_tar_path, "r:") as tar:
tar.extractall(mlf_unpacking_dir)

# Copy C files
model_dir = source_dir / "model"
model_dir.mkdir()
for source, dest in [
("codegen/host/src/default_lib0.c", "default_lib0.c"),
("codegen/host/src/default_lib1.c", "default_lib1.c"),
]:
shutil.copy(os.path.join(mlf_unpacking_dir, source), model_dir / dest)

# Copy C files from model. The filesnames and quantity
# depend on the target string, so we just copy all c files
source_dir = mlf_unpacking_dir / "codegen" / "host" / "src"
for file in source_dir.rglob(f"*.c"):
shutil.copy(file, model_dir)

# Return metadata.json for use in templating
with open(os.path.join(mlf_unpacking_dir, "metadata.json")) as f:
Expand All @@ -151,39 +197,38 @@ def _template_model_header(self, source_dir, metadata):
with open(source_dir / "model.h", "w") as f:
f.write(model_h_template.substitute(template_values))


# Arduino ONLY recognizes .ino, .ccp, .c, .h

CPP_FILE_EXTENSION_SYNONYMS = ("cc", "cxx")

def _change_cpp_file_extensions(self, source_dir):
for ext in self.CPP_FILE_EXTENSION_SYNONYMS:
for filename in source_dir.rglob(f"*.{ext}"):
filename.rename(filename.with_suffix('.cpp'))
filename.rename(filename.with_suffix(".cpp"))

for filename in source_dir.rglob(f"*.inc"):
filename.rename(filename.with_suffix('.h'))

filename.rename(filename.with_suffix(".h"))

def _process_autogenerated_inc_files(self, source_dir):
for filename in source_dir.rglob(f"*.inc"):
# Individual file fixes
if filename.stem == "gentab_ccitt":
with open(filename, 'r+') as f:
with open(filename, "r+") as f:
content = f.read()
f.seek(0, 0)
f.write('#include "inttypes.h"\n' + content)

filename.rename(filename.with_suffix('.c'))
filename.rename(filename.with_suffix(".c"))

POSSIBLE_BASE_PATHS = ["src/standalone_crt/include/", "src/standalone_crt/crt_config/"]

def _find_modified_include_path(self, project_dir, file_path, import_path):
# If the import is for a .inc file we renamed to .c earlier, fix it
if import_path.endswith(self.CPP_FILE_EXTENSION_SYNONYMS):
import_path = re.sub(r'\.[a-z]+$', ".cpp", import_path)
import_path = re.sub(r"\.[a-z]+$", ".cpp", import_path)

if import_path.endswith(".inc"):
import_path = re.sub(r'\.[a-z]+$', ".h", import_path)
import_path = re.sub(r"\.[a-z]+$", ".h", import_path)

# If the import already works, don't modify it
if (file_path.parents[0] / import_path).exists():
Expand Down Expand Up @@ -239,14 +284,15 @@ def generate_project(self, model_library_format_path, standalone_crt_dir, projec

# Copy standalone_crt into src folder
self._copy_standalone_crt(source_dir, standalone_crt_dir)
self._remove_unused_components(source_dir)
self._remove_unused_components(source_dir, options["project_type"])

# Unpack the MLF and copy the relevant files
metadata = self._disassemble_mlf(model_library_format_path, source_dir)
shutil.copy2(model_library_format_path, source_dir / "model")

# Template model.h with metadata to minimize space usage
self._template_model_header(source_dir, metadata)
# For AOT, template model.h with metadata to minimize space usage
if options["project_type"] == "example_project":
self._template_model_header(source_dir, metadata)

self._change_cpp_file_extensions(source_dir)

Expand Down Expand Up @@ -341,7 +387,17 @@ def open_transport(self, options):
return

port = self._get_arduino_port(options)

# Wait for port to become available
for attempts in range(10):
if any(serial.tools.list_ports.grep(port)):
break
time.sleep(0.5)

# TODO figure out why RPC serial communication times out 90% (not 100%)
# of the time on the Nano 33 BLE
self._serial = serial.Serial(port, baudrate=115200, timeout=5)

return server.TransportTimeouts(
session_start_retry_timeout_sec=2.0,
session_start_timeout_sec=5.0,
Expand All @@ -364,7 +420,7 @@ def read_transport(self, n, timeout_sec):
def write_transport(self, data, timeout_sec):
if self._serial is None:
raise server.TransportClosedError()
return self._serial.write(data, timeout_sec)
return self._serial.write(data)


if __name__ == "__main__":
Expand Down
Loading

0 comments on commit 7fad760

Please sign in to comment.