diff options
Diffstat (limited to 'tools')
| -rwxr-xr-x | tools/build_adafruit_bins.sh | 12 | ||||
| -rw-r--r-- | tools/gen_usb_descriptor.py | 474 | ||||
| -rw-r--r-- | tools/hid_report_descriptors.py | 239 | ||||
| -rwxr-xr-x | tools/mpy-tool.py | 70 | ||||
| -rwxr-xr-x | tools/preprocess_frozen_modules.py | 2 | ||||
| m--------- | tools/usb_descriptor | 0 |
6 files changed, 789 insertions, 8 deletions
diff --git a/tools/build_adafruit_bins.sh b/tools/build_adafruit_bins.sh index 595d5ba5d..4c672c6de 100755 --- a/tools/build_adafruit_bins.sh +++ b/tools/build_adafruit_bins.sh @@ -6,7 +6,9 @@ rm -rf ports/nrf/build* # Alphabetical. HW_BOARDS="\ +arduino_mkr1300 \ arduino_zero \ +catwan_usbstick \ circuitplayground_express \ circuitplayground_express_crickit \ feather_huzzah \ @@ -25,8 +27,10 @@ grandcentral_m4_express \ hallowing_m0_express \ itsybitsy_m0_express \ itsybitsy_m4_express \ +makerdiary_nrf52840_mdk \ metro_m0_express \ metro_m4_express \ +meowmeow \ pca10056 \ pca10059 \ pirkey_m0 \ @@ -66,8 +70,9 @@ for board in $boards; do for language_file in $(ls locale/*.po); do language=$(basename -s .po $language_file) echo "Building $board for $language" + # There is a bug in the Huzzah Makefile that causes it to fail occasionally with -j > 1. if [[ $board == "feather_huzzah" ]]; then - make $PARALLEL -C ports/esp8266 TRANSLATION=$language BOARD=$board + make -C ports/esp8266 TRANSLATION=$language BOARD=$board (( exit_status = exit_status || $? )) temp_filename=ports/esp8266/build/firmware-combined.bin extension=bin @@ -81,6 +86,11 @@ for board in $boards; do (( exit_status = exit_status || $? )) temp_filename=ports/nrf/build-$board-s140/firmware.uf2 extension=uf2 + elif [[ $board == "makerdiary_nrf52840_mdk" ]]; then + make $PARALLEL -C ports/nrf TRANSLATION=$language BOARD=$board SD=s140 + (( exit_status = exit_status || $? )) + temp_filename=ports/nrf/build-$board-s140/firmware.hex + extension=hex else time make $PARALLEL -C ports/atmel-samd TRANSLATION=$language BOARD=$board (( exit_status = exit_status || $? )) diff --git a/tools/gen_usb_descriptor.py b/tools/gen_usb_descriptor.py new file mode 100644 index 000000000..2cb06ec2f --- /dev/null +++ b/tools/gen_usb_descriptor.py @@ -0,0 +1,474 @@ +import argparse + +import os +import sys + +sys.path.append("../../tools/usb_descriptor") + +from adafruit_usb_descriptor import audio, audio10, cdc, hid, midi, msc, standard, util +import hid_report_descriptors + +parser = argparse.ArgumentParser(description='Generate USB descriptors.') +parser.add_argument('--manufacturer', type=str, + help='manufacturer of the device') +parser.add_argument('--product', type=str, + help='product name of the device') +parser.add_argument('--vid', type=lambda x: int(x, 16), + help='vendor id') +parser.add_argument('--pid', type=lambda x: int(x, 16), + help='product id') +parser.add_argument('--serial_number_length', type=int, default=32, + help='length needed for the serial number in digits') +parser.add_argument('--output_c_file', type=argparse.FileType('w'), required=True) +parser.add_argument('--output_h_file', type=argparse.FileType('w'), required=True) + +args = parser.parse_args() + +class StringIndex: + """Assign a monotonically increasing index to each unique string. Start with 0.""" + string_to_index = {} + index_to_variable = {} + strings = [] + + @classmethod + def index(cls, string, *, variable_name = None): + if string in cls.string_to_index: + idx = cls.string_to_index[string] + if not cls.index_to_variable[idx]: + cls.index_to_variable[idx] = variable_name + return idx + else: + idx = len(cls.strings) + cls.string_to_index[string] = idx + cls.strings.append(string) + cls.index_to_variable[idx] = variable_name + return idx + + @classmethod + def strings_in_order(cls): + return cls.strings + + + +# langid must be the 0th string descriptor +LANGID_INDEX = StringIndex.index("\u0409", variable_name="language_id") +assert LANGID_INDEX == 0 +SERIAL_NUMBER_INDEX = StringIndex.index("S" * args.serial_number_length, variable_name="usb_serial_number") + +device = standard.DeviceDescriptor( + description="top", + idVendor=args.vid, + idProduct=args.pid, + iManufacturer=StringIndex.index(args.manufacturer), + iProduct=StringIndex.index(args.product), + iSerialNumber=SERIAL_NUMBER_INDEX) + +# Interface numbers are interface-set local and endpoints are interface local +# until util.join_interfaces renumbers them. + +cdc_union = cdc.Union( + description="CDC comm", + bMasterInterface=0x00, # Adjust this after interfaces are renumbered. + bSlaveInterface_list=[0x01]) # Adjust this after interfaces are renumbered. + +cdc_call_management = cdc.CallManagement( + description="CDC comm", + bmCapabilities=0x01, + bDataInterface=0x01) # Adjust this after interfaces are renumbered. + +cdc_comm_interface = standard.InterfaceDescriptor( + description="CDC comm", + bInterfaceClass=cdc.CDC_CLASS_COMM, # Communications Device Class + bInterfaceSubClass=cdc.CDC_SUBCLASS_ACM, # Abstract control model + bInterfaceProtocol=cdc.CDC_PROTOCOL_NONE, + iInterface=StringIndex.index("CircuitPython CDC control"), + subdescriptors=[ + cdc.Header( + description="CDC comm", + bcdCDC=0x0110), + cdc_call_management, + cdc.AbstractControlManagement( + description="CDC comm", + bmCapabilities=0x02), + cdc_union, + standard.EndpointDescriptor( + description="CDC comm in", + bEndpointAddress=0x0 | standard.EndpointDescriptor.DIRECTION_IN, + bmAttributes=standard.EndpointDescriptor.TYPE_INTERRUPT, + wMaxPacketSize=0x0040, + bInterval=0x10) + ]) + +cdc_data_interface = standard.InterfaceDescriptor( + description="CDC data", + bInterfaceClass=cdc.CDC_CLASS_DATA, + iInterface=StringIndex.index("CircuitPython CDC data"), + subdescriptors=[ + standard.EndpointDescriptor( + description="CDC data out", + bEndpointAddress=0x0 | standard.EndpointDescriptor.DIRECTION_OUT, + bmAttributes=standard.EndpointDescriptor.TYPE_BULK), + standard.EndpointDescriptor( + description="CDC data in", + bEndpointAddress=0x0 | standard.EndpointDescriptor.DIRECTION_IN, + bmAttributes=standard.EndpointDescriptor.TYPE_BULK), + ]) + +cdc_interfaces = [cdc_comm_interface, cdc_data_interface] + +msc_interfaces = [ + standard.InterfaceDescriptor( + description="MSC", + bInterfaceClass=msc.MSC_CLASS, + bInterfaceSubClass=msc.MSC_SUBCLASS_TRANSPARENT, + bInterfaceProtocol=msc.MSC_PROTOCOL_BULK, + iInterface=StringIndex.index("CircuitPython Mass Storage"), + subdescriptors=[ + standard.EndpointDescriptor( + description="MSC in", + bEndpointAddress=0x0 | standard.EndpointDescriptor.DIRECTION_IN, + bmAttributes=standard.EndpointDescriptor.TYPE_BULK, + bInterval=0), + standard.EndpointDescriptor( + description="MSC out", + bEndpointAddress=0x1 | standard.EndpointDescriptor.DIRECTION_OUT, + bmAttributes=standard.EndpointDescriptor.TYPE_BULK, + bInterval=0) + ] + ) +] + +# Include only these HID devices. +# DIGITIZER works on Linux but conflicts with MOUSE, so leave it out for now. +hid_devices = ("KEYBOARD", "MOUSE", "CONSUMER", "GAMEPAD") + +combined_hid_report_descriptor = hid.ReportDescriptor( + description="MULTIDEVICE", + report_descriptor=b''.join( + hid_report_descriptors.REPORT_DESCRIPTORS[name].report_descriptor for name in hid_devices )) + +hid_report_ids_dict = { name: hid_report_descriptors.REPORT_IDS[name] for name in hid_devices } +hid_report_lengths_dict = { name: hid_report_descriptors.REPORT_LENGTHS[name] for name in hid_devices } +hid_max_report_length = max(hid_report_lengths_dict.values()) + +# ASF4 expects keyboard and generic devices to have both in and out endpoints, +# and will fail (possibly silently) if both are not supplied. +hid_endpoint_in_descriptor = standard.EndpointDescriptor( + description="HID in", + bEndpointAddress=0x0 | standard.EndpointDescriptor.DIRECTION_IN, + bmAttributes=standard.EndpointDescriptor.TYPE_INTERRUPT, + bInterval=10) + +hid_interfaces = [ + standard.InterfaceDescriptor( + description="HID Multiple Devices", + bInterfaceClass=hid.HID_CLASS, + bInterfaceSubClass=hid.HID_SUBCLASS_NOBOOT, + bInterfaceProtocol=hid.HID_PROTOCOL_NONE, + iInterface=StringIndex.index("CircuitPython HID"), + subdescriptors=[ + hid.HIDDescriptor( + description="HID", + wDescriptorLength=len(bytes(combined_hid_report_descriptor))), + hid_endpoint_in_descriptor, + ] + ), + ] + +# Audio! +midi_in_jack = midi.InJackDescriptor( + description="MIDI PC <- CircuitPython internals", + bJackType=midi.JACK_TYPE_EMBEDDED, + iJack=0) +midi_out_jack = midi.OutJackDescriptor( + description="MIDI PC -> CircuitPython internals", + bJackType=midi.JACK_TYPE_EMBEDDED, + iJack=0) +audio_midi_interface = standard.InterfaceDescriptor( + description="All the audio", + bInterfaceClass=audio.AUDIO_CLASS_DEVICE, + bInterfaceSubClass=audio.AUDIO_SUBCLASS_MIDI_STREAMING, + bInterfaceProtocol=audio.AUDIO_PROTOCOL_V1, + iInterface=StringIndex.index("CircuitPython MIDI"), + subdescriptors=[ + midi.Header( + jacks_and_elements=[ + midi_in_jack, + midi.InJackDescriptor( + description="MIDI data in from user code.", + bJackType=midi.JACK_TYPE_EXTERNAL, iJack=0), + midi_out_jack, + midi.OutJackDescriptor( + description="MIDI data out to user code.", + bJackType=midi.JACK_TYPE_EXTERNAL, iJack=0), + ] + ), + standard.EndpointDescriptor( + description="MIDI data out", + bEndpointAddress=0x0 | standard.EndpointDescriptor.DIRECTION_OUT, + bmAttributes=standard.EndpointDescriptor.TYPE_BULK), + midi.DataEndpointDescriptor(baAssocJack=[midi_out_jack]), + standard.EndpointDescriptor( + description="MIDI data in", + bEndpointAddress=0x0 | standard.EndpointDescriptor.DIRECTION_IN, + bmAttributes=standard.EndpointDescriptor.TYPE_BULK), + midi.DataEndpointDescriptor(baAssocJack=[midi_in_jack]), + ]) + +cs_ac_interface = audio10.AudioControlInterface( + description="Empty audio control", + audio_streaming_interfaces = [], + midi_streaming_interfaces = [ + audio_midi_interface + ] + ) + +audio_control_interface = standard.InterfaceDescriptor( + description="All the audio", + bInterfaceClass=audio.AUDIO_CLASS_DEVICE, + bInterfaceSubClass=audio.AUDIO_SUBCLASS_CONTROL, + bInterfaceProtocol=audio.AUDIO_PROTOCOL_V1, + iInterface=StringIndex.index("CircuitPython Audio"), + subdescriptors=[ + cs_ac_interface, + ]) + +# Audio streaming interfaces must occur before MIDI ones. +# audio_interfaces = [audio_control_interface] + cs_ac_interface.audio_streaming_interfaces + cs_ac_interface.midi_streaming_interfaces + +# This will renumber the endpoints to make them unique across descriptors, +# and renumber the interfaces in order. But we still need to fix up certain +# interface cross-references. +interfaces = util.join_interfaces(cdc_interfaces, msc_interfaces, hid_interfaces) + +# Now adjust the CDC interface cross-references. + +cdc_union.bMasterInterface = cdc_comm_interface.bInterfaceNumber +cdc_union.bSlaveInterface_list = [cdc_data_interface.bInterfaceNumber] + +cdc_call_management.bDataInterface = cdc_data_interface.bInterfaceNumber + +cdc_iad = standard.InterfaceAssociationDescriptor( + description="CDC IAD", + bFirstInterface=cdc_comm_interface.bInterfaceNumber, + bInterfaceCount=len(cdc_interfaces), + bFunctionClass=cdc.CDC_CLASS_COMM, # Communications Device Class + bFunctionSubClass=cdc.CDC_SUBCLASS_ACM, # Abstract control model + bFunctionProtocol=cdc.CDC_PROTOCOL_NONE) + +# audio_iad = standard.InterfaceAssociationDescriptor( +# description="Audio IAD", +# bFirstInterface=audio_control_interface.bInterfaceNumber, +# bInterfaceCount=len(audio_interfaces), +# bFunctionClass=audio.AUDIO_CLASS_DEVICE, +# bFunctionSubClass=audio.AUDIO_SUBCLASS_UNKNOWN, +# bFunctionProtocol=audio.AUDIO_PROTOCOL_V1) + + +descriptor_list = [] +descriptor_list.append(cdc_iad) +# descriptor_list.append(audio_iad) +descriptor_list.extend(cdc_interfaces) +descriptor_list.extend(msc_interfaces) +# descriptor_list.append(audio_control_interface) +# Put the CDC IAD just before the CDC interfaces. +# There appears to be a bug in the Windows composite USB driver that requests the +# HID report descriptor with the wrong interface number if the HID interface is not given +# first. However, it still fetches the descriptor anyway. We could reorder the interfaces but +# the Windows 7 Adafruit_usbser.inf file thinks CDC is at Interface 0, so we'll leave it +# there for backwards compatibility. +descriptor_list.extend(hid_interfaces) + +configuration = standard.ConfigurationDescriptor( + description="Composite configuration", + wTotalLength=(standard.ConfigurationDescriptor.bLength + + sum([len(bytes(x)) for x in descriptor_list])), + bNumInterfaces=len(interfaces)) +descriptor_list.insert(0, configuration) + +string_descriptors = [standard.StringDescriptor(string) for string in StringIndex.strings_in_order()] +serial_number_descriptor = string_descriptors[SERIAL_NUMBER_INDEX] + +c_file = args.output_c_file +h_file = args.output_h_file + + +c_file.write("""\ +#include <stdint.h> + +#include "{H_FILE_NAME}" + +""".format(H_FILE_NAME=h_file.name)) + +c_file.write("""\ +// {DESCRIPTION} : {CLASS} +""".format(DESCRIPTION=device.description, + CLASS=device.__class__)) + +c_file.write("""\ +const uint8_t usb_desc_dev[] = { +""") +for b in bytes(device): + c_file.write("0x{:02x}, ".format(b)) + +c_file.write("""\ +}; +""") + +c_file.write("""\ +const uint8_t usb_desc_cfg[] = { +""") + +# Write out all the regular descriptors as one long array (that's how ASF4 does it). +descriptor_length = 0 +for descriptor in descriptor_list: + c_file.write("""\ +// {DESCRIPTION} : {CLASS} +""".format(DESCRIPTION=descriptor.description, + CLASS=descriptor.__class__)) + + b = bytes(descriptor) + notes = descriptor.notes() + i = 0 + + # This prints each subdescriptor on a separate line. + n = 0 + while i < len(b): + length = b[i] + for j in range(length): + c_file.write("0x{:02x}, ".format(b[i + j])) + c_file.write("// " + notes[n]) + n += 1 + c_file.write("\n") + i += length + descriptor_length += len(b) + +c_file.write("""\ +}; +""") + +pointers_to_strings = [] + +for idx, descriptor in enumerate(string_descriptors): + c_file.write("""\ +// {DESCRIPTION} : {CLASS} +""".format(DESCRIPTION=descriptor.description, + CLASS=descriptor.__class__)) + + b = bytes(descriptor) + notes = descriptor.notes() + i = 0 + + # This prints each subdescriptor on a separate line. + variable_name = StringIndex.index_to_variable[idx] + if not variable_name: + variable_name = "string_descriptor{}".format(idx) + + const = "const " + if variable_name == "usb_serial_number": + const = "" + c_file.write("""\ +{const}uint16_t {NAME}[] = {{ +""".format(const=const, NAME=variable_name)) + pointers_to_strings.append("{name}".format(name=variable_name)) + n = 0 + while i < len(b): + length = b[i] + for j in range(length // 2): + c_file.write("0x{:04x}, ".format(b[i + 2*j + 1] << 8 | b[i + 2*j])) + n += 1 + c_file.write("\n") + i += length + c_file.write("""\ +}; +""") + +c_file.write("""\ +// array of pointer to string descriptors +uint16_t const * const string_desc_arr [] = +{ +""") +c_file.write(""",\ + +""".join(pointers_to_strings)) + +c_file.write(""" +}; +""") + +c_file.write("\n"); + +hid_descriptor_length = len(bytes(combined_hid_report_descriptor)) + +# Now we values we need for the .h file. +h_file.write("""\ +#ifndef MICROPY_INCLUDED_AUTOGEN_USB_DESCRIPTOR_H +#define MICROPY_INCLUDED_AUTOGEN_USB_DESCRIPTOR_H + +#include <stdint.h> + +const uint8_t usb_desc_dev[{device_length}]; +// Make sure the control buffer is big enough to fit the descriptor. +#define CFG_TUD_ENUM_BUFFER_SIZE {max_configuration_length} +const uint8_t usb_desc_cfg[{configuration_length}]; +uint16_t usb_serial_number[{serial_number_length}]; +uint16_t const * const string_desc_arr [{string_descriptor_length}]; + +const uint8_t hid_report_descriptor[{HID_REPORT_DESCRIPTOR_LENGTH}]; + +// Vendor name included in Inquiry response, max 8 bytes +#define CFG_TUD_MSC_VENDOR "{msc_vendor}" + +// Product name included in Inquiry response, max 16 bytes +#define CFG_TUD_MSC_PRODUCT "{msc_product}" + +""" +.format(serial_number_length=len(bytes(serial_number_descriptor)) // 2, + device_length=len(bytes(device)), + configuration_length=descriptor_length, + max_configuration_length=max(hid_descriptor_length, descriptor_length), + string_descriptor_length=len(pointers_to_strings), + HID_REPORT_DESCRIPTOR_LENGTH=len(bytes(combined_hid_report_descriptor)), + msc_vendor=args.manufacturer[:8], + msc_product=args.product[:16])) + +# #define the report ID's used in the combined HID descriptor +for name, id in hid_report_ids_dict.items(): + h_file.write("""\ +#define USB_HID_REPORT_ID_{name} {id} +""".format(name=name, + id=id)) + +h_file.write("\n") + +# #define the report sizes used in the combined HID descriptor +for name, length in hid_report_lengths_dict.items(): + h_file.write("""\ +#define USB_HID_REPORT_LENGTH_{name} {length} +""".format(name=name, + length=length)) + +h_file.write("\n") + +h_file.write("""\ +#define USB_HID_NUM_DEVICES {num_devices} +#define USB_HID_MAX_REPORT_LENGTH {max_length} +""".format(num_devices=len(hid_report_lengths_dict), + max_length=hid_max_report_length)) + + + +# Write out the report descriptor and info +c_file.write("""\ +const uint8_t hid_report_descriptor[{HID_DESCRIPTOR_LENGTH}] = {{ +""".format(HID_DESCRIPTOR_LENGTH=hid_descriptor_length)) + +for b in bytes(combined_hid_report_descriptor): + c_file.write("0x{:02x}, ".format(b)) +c_file.write(""" +}; +""") + +h_file.write("""\ +#endif // MICROPY_INCLUDED_AUTOGEN_USB_DESCRIPTOR_H +""") diff --git a/tools/hid_report_descriptors.py b/tools/hid_report_descriptors.py new file mode 100644 index 000000000..f3b28ebcf --- /dev/null +++ b/tools/hid_report_descriptors.py @@ -0,0 +1,239 @@ +# The MIT License (MIT) +# +# Copyright (c) 2018 Dan Halbert for Adafruit Industries +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +import struct + +""" +HID specific descriptors +======================== + +* Author(s): Dan Halbert +""" + +from adafruit_usb_descriptor import hid + +REPORT_IDS = { + "KEYBOARD" : 1, + "MOUSE" : 2, + "CONSUMER" : 3, + "SYS_CONTROL" : 4, + "GAMEPAD" : 5, + "DIGITIZER" : 6, + } + +# Byte count for each kind of report. Length does not include report ID in first byte. +REPORT_LENGTHS = { + "KEYBOARD" : 8, + "MOUSE" : 4, + "CONSUMER" : 2, + "SYS_CONTROL" : 1, + "GAMEPAD" : 6, + "DIGITIZER" : 5, + } + +KEYBOARD_WITH_ID = hid.ReportDescriptor( + description="KEYBOARD", + report_descriptor=bytes([ + # Regular keyboard + 0x05, 0x01, # Usage Page (Generic Desktop) + 0x09, 0x06, # Usage (Keyboard) + 0xA1, 0x01, # Collection (Application) + 0x85, REPORT_IDS["KEYBOARD"], # Report ID (1) + 0x05, 0x07, # Usage Page (Keyboard) + 0x19, 224, # Usage Minimum (224) + 0x29, 231, # Usage Maximum (231) + 0x15, 0x00, # Logical Minimum (0) + 0x25, 0x01, # Logical Maximum (1) + 0x75, 0x01, # Report Size (1) + 0x95, 0x08, # Report Count (8) + 0x81, 0x02, # Input (Data, Variable, Absolute) + 0x81, 0x01, # Input (Constant) + 0x19, 0x00, # Usage Minimum (0) + 0x29, 101, # Usage Maximum (101) + 0x15, 0x00, # Logical Minimum (0) + 0x25, 101, # Logical Maximum (101) + 0x75, 0x08, # Report Size (8) + 0x95, 0x06, # Report Count (6) + 0x81, 0x00, # Input (Data, Array) + 0x05, 0x08, # Usage Page (LED) + 0x19, 0x01, # Usage Minimum (1) + 0x29, 0x05, # Usage Maximum (5) + 0x15, 0x00, # Logical Minimum (0) + 0x25, 0x01, # Logical Maximum (1) + 0x75, 0x01, # Report Size (1) + 0x95, 0x05, # Report Count (5) + 0x91, 0x02, # Output (Data, Variable, Absolute) + 0x95, 0x03, # Report Count (3) + 0x91, 0x01, # Output (Constant) + 0xC0, # End Collection + ])) + +MOUSE_WITH_ID = hid.ReportDescriptor( + description="MOUSE", + report_descriptor=bytes([ + # Regular mouse + 0x05, 0x01, # Usage Page (Generic Desktop) + 0x09, 0x02, # Usage (Mouse) + 0xA1, 0x01, # Collection (Application) + 0x09, 0x01, # Usage (Pointer) + 0xA1, 0x00, # Collection (Physical) + 0x85, REPORT_IDS["MOUSE"], # Report ID (n) + 0x05, 0x09, # Usage Page (Button) + 0x19, 0x01, # Usage Minimum (0x01) + 0x29, 0x05, # Usage Maximum (0x05) + 0x15, 0x00, # Logical Minimum (0) + 0x25, 0x01, # Logical Maximum (1) + 0x95, 0x05, # Report Count (5) + 0x75, 0x01, # Report Size (1) + 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x95, 0x01, # Report Count (1) + 0x75, 0x03, # Report Size (3) + 0x81, 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x05, 0x01, # Usage Page (Generic Desktop Ctrls) + 0x09, 0x30, # Usage (X) + 0x09, 0x31, # Usage (Y) + 0x15, 0x81, # Logical Minimum (-127) + 0x25, 0x7F, # Logical Maximum (127) + 0x75, 0x08, # Report Size (8) + 0x95, 0x02, # Report Count (2) + 0x81, 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) + 0x09, 0x38, # Usage (Wheel) + 0x15, 0x81, # Logical Minimum (-127) + 0x25, 0x7F, # Logical Maximum (127) + 0x75, 0x08, # Report Size (8) + 0x95, 0x01, # Report Count (1) + 0x81, 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, # End Collection + 0xC0, # End Collection + ])) + +CONSUMER_WITH_ID = hid.ReportDescriptor( + description="CONSUMER", + report_descriptor=bytes([ + # Consumer ("multimedia") keys + 0x05, 0x0C, # Usage Page (Consumer) + 0x09, 0x01, # Usage (Consumer Control) + 0xA1, 0x01, # Collection (Application) + 0x85, REPORT_IDS["CONSUMER"], # Report ID (n) + 0x75, 0x10, # Report Size (16) + 0x95, 0x01, # Report Count (1) + 0x15, 0x01, # Logical Minimum (1) + 0x26, 0x8C, 0x02, # Logical Maximum (652) + 0x19, 0x01, # Usage Minimum (Consumer Control) + 0x2A, 0x8C, 0x02, # Usage Maximum (AC Send) + 0x81, 0x00, # Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, # End Collection + ])) + +SYS_CONTROL_WITH_ID = hid.ReportDescriptor( + description="SYS_CONTROL", + report_descriptor=bytes([ + # Power controls + 0x05, 0x01, # Usage Page (Generic Desktop Ctrls) + 0x09, 0x80, # Usage (Sys Control) + 0xA1, 0x01, # Collection (Application) + 0x85, REPORT_IDS["SYS_CONTROL"], # Report ID (n) + 0x75, 0x02, # Report Size (2) + 0x95, 0x01, # Report Count (1) + 0x15, 0x01, # Logical Minimum (1) + 0x25, 0x03, # Logical Maximum (3) + 0x09, 0x82, # Usage (Sys Sleep) + 0x09, 0x81, # Usage (Sys Power Down) + 0x09, 0x83, # Usage (Sys Wake Up) + 0x81, 0x60, # Input (Data,Array,Abs,No Wrap,Linear,No Preferred State,Null State) + 0x75, 0x06, # Report Size (6) + 0x81, 0x03, # Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, # End Collection + ])) + +GAMEPAD_WITH_ID = hid.ReportDescriptor( + description="GAMEPAD", + report_descriptor=bytes([ + # Gamepad with 16 buttons and two joysticks + 0x05, 0x01, # Usage Page (Generic Desktop Ctrls) + 0x09, 0x05, # Usage (Game Pad) + 0xA1, 0x01, # Collection (Application) + 0x85, REPORT_IDS["GAMEPAD"], # Report ID (n) + 0x05, 0x09, # Usage Page (Button) + 0x19, 0x01, # Usage Minimum (Button 1) + 0x29, 0x10, # Usage Maximum (Button 16) + 0x15, 0x00, # Logical Minimum (0) + 0x25, 0x01, # Logical Maximum (1) + 0x75, 0x01, # Report Size (1) + 0x95, 0x10, # Report Count (16) + 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x05, 0x01, # Usage Page (Generic Desktop Ctrls) + 0x15, 0x81, # Logical Minimum (-127) + 0x25, 0x7F, # Logical Maximum (127) + 0x09, 0x30, # Usage (X) + 0x09, 0x31, # Usage (Y) + 0x09, 0x32, # Usage (Z) + 0x09, 0x35, # Usage (Rz) + 0x75, 0x08, # Report Size (8) + 0x95, 0x04, # Report Count (4) + 0x81, 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, # End Collection + ])) + +DIGITIZER_WITH_ID = hid.ReportDescriptor( + description="DIGITIZER", + report_descriptor=bytes([ + # Digitizer (used as an absolute pointer) + 0x05, 0x0D, # Usage Page (Digitizers) + 0x09, 0x02, # Usage (Pen) + 0xA1, 0x01, # Collection (Application) + 0x85, REPORT_IDS["DIGITIZER"], # Report ID (n) + 0x09, 0x01, # Usage (Stylus) + 0xA1, 0x00, # Collection (Physical) + 0x09, 0x32, # Usage (In-Range) + 0x09, 0x42, # Usage (Tip Switch) + 0x09, 0x44, # Usage (Barrel Switch) + 0x09, 0x45, # Usage (Eraser Switch) + 0x15, 0x00, # Logical Minimum (0) + 0x25, 0x01, # Logical Maximum (1) + 0x75, 0x01, # Report Size (1) + 0x95, 0x04, # Report Count (4) + 0x81, 0x02, # Input (Data,Var,Abs) + 0x75, 0x04, # Report Size (4) -- Filler + 0x95, 0x01, # Report Count (1) -- Filler + 0x81, 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x05, 0x01, # Usage Page (Generic Desktop Ctrls) + 0x15, 0x00, # Logical Minimum (0) + 0x26, 0xff, 0x7f, # Logical Maximum (32767) + 0x09, 0x30, # Usage (X) + 0x09, 0x31, # Usage (Y) + 0x75, 0x10, # Report Size (16) + 0x95, 0x02, # Report Count (2) + 0x81, 0x02, # Input (Data,Var,Abs) + 0xC0, # End Collection + 0xC0, # End Collection + ])) + +# Byte count for each kind of report. Length does not include report ID in first byte. +REPORT_DESCRIPTORS = { + "KEYBOARD" : KEYBOARD_WITH_ID, + "MOUSE" : MOUSE_WITH_ID, + "CONSUMER" : CONSUMER_WITH_ID, + "SYS_CONTROL" : SYS_CONTROL_WITH_ID, + "GAMEPAD" : GAMEPAD_WITH_ID, + "DIGITIZER" : DIGITIZER_WITH_ID, + } diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py index 7deb76a8d..5ce24061b 100755 --- a/tools/mpy-tool.py +++ b/tools/mpy-tool.py @@ -78,6 +78,18 @@ MP_BC_LOAD_GLOBAL = 0x1d MP_BC_LOAD_ATTR = 0x1e MP_BC_STORE_ATTR = 0x26 +# load opcode names +opcode_names = {} +with open("../../py/bc0.h") as f: + for line in f.readlines(): + if line.startswith("#define"): + s = line.split(maxsplit=3) + if len(s) < 3: + continue + _, name, value = s[:3] + opcode = int(value.strip("()"), 0) + opcode_names[opcode] = name + def make_opcode_format(): def OC4(a, b, c, d): return a | (b << 2) | (c << 4) | (d << 6) @@ -252,35 +264,50 @@ class RawCode: i += 1 RawCode.escaped_names.add(self.escaped_name) + sizes = {"bytecode": 0, "strings": 0, "raw_code_overhead": 0, "const_table_overhead": 0, "string_overhead": 0, "number_overhead": 0} # emit children first for rc in self.raw_codes: - rc.freeze(self.escaped_name + '_') + subsize = rc.freeze(self.escaped_name + '_') + for k in sizes: + sizes[k] += subsize[k] + # generate bytecode data print() print('// frozen bytecode for file %s, scope %s%s' % (self.source_file.str, parent_name, self.simple_name.str)) + print("// bytecode size", len(self.bytecode)) print('STATIC ', end='') if not config.MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE: print('const ', end='') print('byte bytecode_data_%s[%u] = {' % (self.escaped_name, len(self.bytecode))) + sizes["bytecode"] += len(self.bytecode) print(' ', end='') for i in range(self.ip2): print(' 0x%02x,' % self.bytecode[i], end='') print() + print(" // simple name") print(' ', self.simple_name.qstr_id, '& 0xff,', self.simple_name.qstr_id, '>> 8,') + print(" // source file") print(' ', self.source_file.qstr_id, '& 0xff,', self.source_file.qstr_id, '>> 8,') + print(" // code info") print(' ', end='') for i in range(self.ip2 + 4, self.ip): print(' 0x%02x,' % self.bytecode[i], end='') print() + print(" // bytecode") ip = self.ip while ip < len(self.bytecode): f, sz = mp_opcode_format(self.bytecode, ip) + opcode = self.bytecode[ip] + if opcode in opcode_names: + opcode = opcode_names[opcode] + else: + opcode = '0x%02x' % opcode if f == 1: qst = self._unpack_qstr(ip + 1).qstr_id - print(' ', '0x%02x,' % self.bytecode[ip], qst, '& 0xff,', qst, '>> 8,') + print(' {}, {} & 0xff, {} >> 8,'.format(opcode, qst, qst)) else: - print(' ', ''.join('0x%02x, ' % self.bytecode[ip + i] for i in range(sz))) + print(' {},{}'.format(opcode, ''.join(' 0x%02x,' % self.bytecode[ip + i] for i in range(1, sz)))) ip += sz print('};') @@ -295,9 +322,12 @@ class RawCode: obj_type = 'mp_type_str' else: obj_type = 'mp_type_bytes' - print('STATIC const mp_obj_str_t %s = {{&%s}, %u, %u, (const byte*)"%s"};' + print('STATIC const mp_obj_str_t %s = {{&%s}, %u, %u, (const byte*)"%s"}; // %s' % (obj_name, obj_type, qstrutil.compute_hash(obj, config.MICROPY_QSTR_BYTES_IN_HASH), - len(obj), ''.join(('\\x%02x' % b) for b in obj))) + len(obj), ''.join(('\\x%02x' % b) for b in obj), obj)) + sizes["strings"] += len(obj) + sizes["string_overhead"] += 16 + elif is_int_type(obj): if config.MICROPY_LONGINT_IMPL == config.MICROPY_LONGINT_IMPL_NONE: # TODO check if we can actually fit this long-int into a small-int @@ -321,14 +351,17 @@ class RawCode: print('STATIC const mp_obj_int_t %s = {{&mp_type_int}, ' '{.neg=%u, .fixed_dig=1, .alloc=%u, .len=%u, .dig=(uint%u_t[]){%s}}};' % (obj_name, neg, ndigs, ndigs, bits_per_dig, digs)) + sizes["number_overhead"] += 16 elif type(obj) is float: print('#if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_A || MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_B') print('STATIC const mp_obj_float_t %s = {{&mp_type_float}, %.16g};' % (obj_name, obj)) print('#endif') + sizes["number_overhead"] += 8 elif type(obj) is complex: print('STATIC const mp_obj_complex_t %s = {{&mp_type_complex}, %.16g, %.16g};' % (obj_name, obj.real, obj.imag)) + sizes["number_overhead"] += 12 else: raise FreezeError(self, 'freezing of object %r is not implemented' % (obj,)) @@ -338,8 +371,10 @@ class RawCode: print('STATIC const mp_rom_obj_t const_table_data_%s[%u] = {' % (self.escaped_name, const_table_len)) for qst in self.qstrs: + sizes["const_table_overhead"] += 4 print(' MP_ROM_QSTR(%s),' % global_qstrs[qst].qstr_id) for i in range(len(self.objs)): + sizes["const_table_overhead"] += 4 if type(self.objs[i]) is float: print('#if MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_A || MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_B') print(' MP_ROM_PTR(&const_obj_%s_%u),' % (self.escaped_name, i)) @@ -353,6 +388,7 @@ class RawCode: else: print(' MP_ROM_PTR(&const_obj_%s_%u),' % (self.escaped_name, i)) for rc in self.raw_codes: + sizes["const_table_overhead"] += 4 print(' MP_ROM_PTR(&raw_code_%s),' % rc.escaped_name) print('};') @@ -376,6 +412,9 @@ class RawCode: print(' #endif') print(' },') print('};') + sizes["raw_code_overhead"] += 16 + + return sizes def read_uint(f): i = 0 @@ -467,6 +506,7 @@ def freeze_mpy(base_qstrs, raw_codes): new[q.qstr_esc] = (len(new), q.qstr_esc, q.str) new = sorted(new.values(), key=lambda x: x[0]) + print('#include "py/bc0.h"') print('#include "py/mpconfig.h"') print('#include "py/objint.h"') print('#include "py/objstr.h"') @@ -523,27 +563,45 @@ def freeze_mpy(base_qstrs, raw_codes): print(' %u, // allocated entries' % len(new)) print(' %u, // used entries' % len(new)) print(' {') + qstr_size = {"metadata": 0, "data": 0} for _, _, qstr in new: + qstr_size["metadata"] += config.MICROPY_QSTR_BYTES_IN_LEN + config.MICROPY_QSTR_BYTES_IN_HASH + qstr_size["data"] += len(qstr) print(' %s,' % qstrutil.make_bytes(config.MICROPY_QSTR_BYTES_IN_LEN, config.MICROPY_QSTR_BYTES_IN_HASH, qstr)) print(' },') print('};') + sizes = {} for rc in raw_codes: - rc.freeze(rc.source_file.str.replace('/', '_')[:-3] + '_') + sizes[rc.source_file.str] = rc.freeze(rc.source_file.str.replace('/', '_')[:-3] + '_') print() print('const char mp_frozen_mpy_names[] = {') + qstr_size["filenames"] = 1 for rc in raw_codes: module_name = rc.source_file.str print('"%s\\0"' % module_name) + qstr_size["filenames"] += len(module_name) + 1 print('"\\0"};') print('const mp_raw_code_t *const mp_frozen_mpy_content[] = {') for rc in raw_codes: print(' &raw_code_%s,' % rc.escaped_name) + size = sizes[rc.source_file.str] + print(' // Total size:', sum(size.values())) + for k in size: + print(" // {} {}".format(k, size[k])) print('};') + print() + print('// Total size:', sum([sum(x.values()) for x in sizes.values()]) + sum(qstr_size.values())) + for k in size: + total = sum([x[k] for x in sizes.values()]) + print("// {} {}".format(k, total)) + for k in qstr_size: + print("// qstr {} {}".format(k, qstr_size[k])) + def main(): import argparse cmd_parser = argparse.ArgumentParser(description='A tool to work with MicroPython .mpy files.') diff --git a/tools/preprocess_frozen_modules.py b/tools/preprocess_frozen_modules.py index d157deeee..bb6959f0d 100755 --- a/tools/preprocess_frozen_modules.py +++ b/tools/preprocess_frozen_modules.py @@ -33,7 +33,7 @@ def copy_and_process(in_dir, out_dir): for root, subdirs, files in os.walk(in_dir): # Skip library examples directories. - if Path(root).name in ['examples', 'docs']: + if Path(root).name in ['examples', 'docs', 'tests']: continue for file in files: diff --git a/tools/usb_descriptor b/tools/usb_descriptor -Subproject 2507847031a0395465956539253cbfa27f87511 +Subproject 57bf602dac9cba8c4226f764c286cbc60103d67 |
