From 82313713d8753e228f7dcb36a1b280ed6a36d765 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sun, 11 Mar 2018 19:59:23 -0700 Subject: [PATCH 001/215] [python tools] use IPython.embed() by default exmplore_odrive.py no longer needs to be executed with "ipython -i explore_odrive.py" but can launch an embedded IPython command prompt by itself. If IPython is not available, the script falls back to checking for normal python interactive mode. --- tools/explore_odrive.py | 49 +++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/tools/explore_odrive.py b/tools/explore_odrive.py index 59492a30..9efaebb7 100755 --- a/tools/explore_odrive.py +++ b/tools/explore_odrive.py @@ -6,6 +6,35 @@ Load an odrive object to play with in the IPython interactive shell. import odrive.core import argparse import sys +import platform + +# Check if IPython is installed +try: + import IPython + embed_ipython = True +except: + embed_ipython = False + + print("Warning: you don't have IPython installed.") + print("If you want to have an improved interactive console with pretty colors,") + print("you should install IPython\n") + + # Ensure interactive mode + if not bool(getattr(sys, 'ps1', sys.flags.interactive)): + print("You're not running in interactive mode. Run python -i explore_odrive.py") + print('') + sys.exit(1) + + # Enable tab complete if possible + try: + import readline + readline.parse_and_bind("tab: complete") + except: + sudo_prefix = "" if platform.system() == "Windows" else "sudo " + print("Warning: could not enable tab-complete. User experience will suffer.\n" + "Run `{}pip install readline` and then restart this script to fix this." + .format(sudo_prefix)) + # some enums described in the README # TODO: transmit as part of the JSON @@ -80,20 +109,6 @@ print('and "my_odrive.motor0.pos_setpoint = 10000"') print('will send motor0 to 10000') print('') -try: - # If this assignment works, we are already in interactive mode. - # so just drop out of script to existing shell - interpreter = sys.ps1 -except AttributeError: - # We are not in interactive mode, so let's fire one up - # Though let's be real, IPython is the way to go - print('If you want to have an improved interactive console with pretty colors,') - print('you can run this script in interactive mode with IPython with this command:') - print('ipython -i explore_odrive.py') - print('') - # Enter interactive python shell with tab complete enabled - import code - import rlcompleter - import readline - readline.parse_and_bind("tab: complete") - code.interact(local=locals(), banner='') +# If IPython is installed, embed shell, otherwise drop into interactive stock python shell +if embed_ipython: + IPython.embed() From eab7cda53ea096e7c5ac81d923e9c3346e2921ff Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 12 Mar 2018 17:51:34 -0700 Subject: [PATCH 002/215] make liveplotter not steal focus and terminate as expected --- tools/liveplotter.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) mode change 100644 => 100755 tools/liveplotter.py diff --git a/tools/liveplotter.py b/tools/liveplotter.py old mode 100644 new mode 100755 index d432062b..1c4ba849 --- a/tools/liveplotter.py +++ b/tools/liveplotter.py @@ -19,20 +19,32 @@ plt.ion() global vals vals = [] +# Make sure the script terminates when the user closes the plotter +cancellation_token = threading.Event() +def handle_close(evt): + cancellation_token.set() +fig = plt.figure() +fig.canvas.mpl_connect('close_event', handle_close) + def fetch_data(): global vals - while True: - vals.append(my_odrive.motor0.encoder.pll_pos) + global cancellation_token + while not cancellation_token.is_set(): + vals.append(my_odrive.motor0.timing_log.TIMING_LOG_FOC_CURRENT) if len(vals) > num_samples: vals = vals[-num_samples:] time.sleep(1/data_rate) +# TODO: use animation for better UI performance, see: +# https://matplotlib.org/examples/animation/simple_anim.html def plot_data(): global vals - while True: + global cancellation_token + while not cancellation_token.is_set(): plt.clf() plt.plot(vals) - plt.pause(1/plot_rate) + #time.sleep(1/plot_rate) + fig.canvas.flush_events() fetch_thread = threading.Thread(target=fetch_data, daemon=True) fetch_thread.start() From f1d047958f8b81403cb84b7dbd4926bdd3af429f Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 15 Mar 2018 17:05:58 -0700 Subject: [PATCH 003/215] relax tup environment check When tup sees a change in environment variables, it needs to rebuild everything because it doesn't know if a process accessed the changed environment variable. However these changes are frequent when changing between VSCode and terminal. By disabling this check we trade a tiny bit of correctness for convenience. --- Firmware/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/Makefile b/Firmware/Makefile index 40bbba59..0c6316d6 100644 --- a/Firmware/Makefile +++ b/Firmware/Makefile @@ -7,7 +7,7 @@ FIRMWARE = $(BUILD_DIR)/ODriveFirmware.elf FIRMWARE_HEX = $(BUILD_DIR)/ODriveFirmware.hex all: - @tup --quiet + @tup --quiet --no-environ-check flash: all openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg -c init -c reset\ halt -c flash\ write_image\ erase\ $(FIRMWARE) -c reset\ run -c exit From 3ea4b731b31d2bc25ed89805de93a85b0c8feebf Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 16 Mar 2018 19:47:51 -0700 Subject: [PATCH 004/215] rename USBHaltException to ChannelDamagedException --- tools/odrive/protocol.py | 21 ++++++++++++------- tools/odrive/usbbulk_transport.py | 35 +++++++++++++++++++------------ 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/tools/odrive/protocol.py b/tools/odrive/protocol.py index 18f44a41..4878fadc 100644 --- a/tools/odrive/protocol.py +++ b/tools/odrive/protocol.py @@ -66,14 +66,21 @@ def calc_crc16(remainder, value): class TimeoutException(Exception): pass -class ChannelBrokenException(Exception): - pass - class DeviceInitException(Exception): pass -class USBHaltException(Exception): - pass +class ChannelDamagedException(Exception): + """ + Raised when the channel is temporarily broken and a + resend of the message might be successful + """ + pass + +class ChannelBrokenException(Exception): + """ + Raised when the channel is permanently broken + """ + pass class StreamSource(ABC): @@ -243,7 +250,7 @@ class Channel(PacketSink): while (attempt < self._send_attempts): try: self._output.process_packet(packet) - except USBHaltException: + except ChannelDamagedException: attempt += 1 continue # resend deadline = time.monotonic() + self._resend_timeout @@ -254,7 +261,7 @@ class Channel(PacketSink): response = self._input.get_packet(deadline) except TimeoutException: break # resend - except USBHaltException: + except ChannelDamagedException: break # resend # process response, which is hopefully our ACK self.process_packet(response) diff --git a/tools/odrive/usbbulk_transport.py b/tools/odrive/usbbulk_transport.py index e0bb5a58..aba9a770 100644 --- a/tools/odrive/usbbulk_transport.py +++ b/tools/odrive/usbbulk_transport.py @@ -16,6 +16,7 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) self._printer = printer self.dev = dev self._name = "USB device {}:{}".format(dev.idVendor, dev.idProduct) + self._was_damaged = False ## # information about the connected device @@ -75,36 +76,44 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) def process_packet(self, usbBuffer): try: ret = self.epw.write(usbBuffer, 0) + if self._was_damaged: + self._printer("Recovered from USB halt/stall condition") + self._was_damaged = False return ret except usb.core.USBError as ex: if ex.errno == 19: # "no such device" raise odrive.protocol.ChannelBrokenException() else: # Try resetting halt/stall condition - self.epw.clear_halt() - # Resend - ret = self.epw.write(usbBuffer, 0) - self._printer("Recovered from USB halt/stall condition on write") - return ret - # Signal to retry transfer - # raise odrive.protocol.USBHaltException() + try: + self.epw.clear_halt() + except usb.core.USBError: + raise odrive.protocol.ChannelBrokenException() + # Retry transfer + self._was_damaged = True + raise odrive.protocol.ChannelDamagedException() def get_packet(self, deadline): try: bufferLen = self.epr.wMaxPacketSize timeout = max(int((deadline - time.monotonic()) * 1000), 0) ret = self.epr.read(bufferLen, timeout) + if self._was_damaged: + self._printer("Recovered from USB halt/stall condition") + self._was_damaged = False return bytearray(ret) except usb.core.USBError as ex: if ex.errno == 19: # "no such device" raise odrive.protocol.ChannelBrokenException() else: - # Try resetting halt/stall condition and flush buffer - self.epr.clear_halt() - ret = self.epr.read(bufferLen, timeout) - self._printer("Recovered from USB halt/stall condition on read") - # Signal to retry transfer - raise odrive.protocol.USBHaltException() + # Try resetting halt/stall condition + try: + self.epw.clear_halt() + except usb.core.USBError: + raise odrive.protocol.ChannelBrokenException() + # Retry transfer + self._was_damaged = True + raise odrive.protocol.ChannelDamagedException() def send_max(self): return 64 From 1c2cfd2923a84a986984f5f6986a4f4e1c38fa08 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 16 Mar 2018 22:43:21 -0700 Subject: [PATCH 005/215] [python tools] refactor device discovery and object creation --- tools/demo.py | 7 +- tools/dfu.py | 76 ++----- tools/drv_status.py | 6 +- tools/explore_odrive.py | 176 +++++++++++----- tools/liveplotter.py | 6 +- tools/odrive/core.py | 334 ------------------------------ tools/odrive/discovery.py | 88 ++++++++ tools/odrive/remote_object.py | 212 +++++++++++++++++++ tools/odrive/serial_transport.py | 53 ++++- tools/odrive/usbbulk_transport.py | 68 +++++- tools/odrive/util.py | 23 -- tools/rate_test.py | 9 +- 12 files changed, 578 insertions(+), 480 deletions(-) mode change 100644 => 100755 tools/drv_status.py delete mode 100644 tools/odrive/core.py create mode 100644 tools/odrive/discovery.py create mode 100644 tools/odrive/remote_object.py delete mode 100644 tools/odrive/util.py mode change 100644 => 100755 tools/rate_test.py diff --git a/tools/demo.py b/tools/demo.py index 4a06c260..41360333 100755 --- a/tools/demo.py +++ b/tools/demo.py @@ -5,12 +5,15 @@ Example usage of the ODrive python library to monitor and control ODrive devices from __future__ import print_function -import odrive.core +import odrive.discovery import time import math # Find a connected ODrive (this will block until you connect one) -my_drive = odrive.core.find_any(consider_usb=True, consider_serial=False, printer=print) +my_drive = odrive.discovery.find_any() + +# Find an ODrive that is connected on the serial port /dev/ttyUSB0 +#my_drive = odrive.discovery.find_any("serial:/dev/ttyUSB0") # The above call returns a python object with a dynamically generated type. The # type hierarchy will correspond to the endpoint list in `MotorControl/protocol.cpp`. diff --git a/tools/dfu.py b/tools/dfu.py index 7ad27bf8..cd8553c1 100755 --- a/tools/dfu.py +++ b/tools/dfu.py @@ -12,7 +12,7 @@ import struct import dfuse import usb.core import usb.util -import odrive.core +import odrive.discovery # We are interactively printing status messages, so flush by default import functools @@ -142,18 +142,6 @@ def jump_to_application(dfudev, address): if status[1] != dfuse.DfuState.DFU_MANIFEST: raise RuntimeError("An error occured. Device Status: {}".format(status[1])) -def str_to_uuid(uuid): - uuid = bytearray.fromhex(uuid.replace('-', '')) - return struct.unpack('>I', uuid[0:4]), struct.unpack('>I', uuid[4:8]), struct.unpack('>I', uuid[8:12]) - -def uuid_to_str(uuid0, uuid1, uuid2): - return "{:08X}-{:08X}-{:08X}".format(struct.pack('>I', uuid0), struct.pack('>I', uuid1), struct.pack('>I', uuid2)) - -def uuid_to_serial(uuid0, uuid1, uuid2): - return (struct.pack('>I', uuid0 + uuid2) + struct.pack('>I', uuid1)[0:2]).hex().upper() - - -### THREADS ### def show_deferred_message(message, cancellation_token): """ @@ -170,43 +158,25 @@ def show_deferred_message(message, cancellation_token): t.daemon = True t.start() -def put_odrive_into_dfu_mode_thread(cancellation_token): +def put_odrive_into_dfu_mode(my_drive): """ - Waits for an ODrive with a matching serial number and puts - it into DFU mode once it's found. The thread continues to put - matching devices into DFU mode until cancellation_token - is set. + Puts the specified device into DFU mode """ - global app_cancellation_token - while not cancellation_token.is_set(): - constraints = {} if serial_number == None else {'serial_number': serial_number} - my_drive = odrive.core.find_any(consider_usb=True, consider_serial=False, - cancellation_token=cancellation_token, - **constraints) - if cancellation_token.is_set(): - return - if not hasattr(my_drive, "enter_dfu_mode"): - print("The firmware on device {} does not support DFU. You need to \n" - "flash the firmware once using STLink (`make flash`), after that \n" - "DFU with this script should work fine." - .format(my_drive.__channel__.usb_device.serial_number)) - # Terminate script, otherwise it would try to reconnect to the same - # incompatible device - app_cancellation_token.set() # TODO: implement a more sensible discorvery mechanism to fix this - return - print("Putting device {} into DFU mode...".format(my_drive.__channel__.usb_device.serial_number)) - try: - my_drive.enter_dfu_mode() - except usb.core.USBError as ex: - pass # this is expected because the device reboots - if platform.system() == "Windows": - show_deferred_message("Still waiting for the device to reappear.\n" - "Use the Zadig utility to set the driver of 'STM32 BOOTLOADER' to libusb-win32.", - cancellation_token) - # If we immediately continue we might still pick up the device that was - # just rebooted. This isn't an issue but will display a distracting - # error message. - time.sleep(1) + if not hasattr(my_drive, "enter_dfu_mode"): + print("The firmware on device {} does not support DFU. You need to \n" + "flash the firmware once using STLink (`make flash`), after that \n" + "DFU with this script should work fine." + .format(my_drive.__channel__.usb_device.serial_number)) + return + print("Putting device {} into DFU mode...".format(my_drive.__channel__.usb_device.serial_number)) + try: + my_drive.enter_dfu_mode() + except odrive.protocol.ChannelBrokenException as ex: + pass # this is expected because the device reboots + if platform.system() == "Windows": + show_deferred_message("Still waiting for the device to reappear.\n" + "Use the Zadig utility to set the driver of 'STM32 BOOTLOADER' to libusb-win32.", + find_odrive_cancellation_token) ### BEGINNING OF APPLICATION ### @@ -230,12 +200,7 @@ hexfile = IntelHex(args.file) #for start, end in hexfile.segments(): # print(" {:08X} to {:08X}".format(start, end - 1)) -if args.uuid != None: - serial_number = uuid_to_serial(*str_to_uuid(args.uuid)) -elif args.serial_number != None: - serial_number = args.serial_number -else: - serial_number = None +serial_number = args.serial_number app_cancellation_token = threading.Event() @@ -244,7 +209,8 @@ try: print("Waiting for ODrive...") # Scan for ODrives not in DFU mode and put them into DFU mode once they appear - threading.Thread(target=put_odrive_into_dfu_mode_thread, args=(find_odrive_cancellation_token,)).start() + # We only scan on USB because DFU is only possible over USB + odrive.discovery.find_all("usb", serial_number, put_odrive_into_dfu_mode, find_odrive_cancellation_token) # Poll libUSB until a device in DFU mode is found while not app_cancellation_token.is_set(): diff --git a/tools/drv_status.py b/tools/drv_status.py old mode 100644 new mode 100755 index fda6c292..bc62c3ed --- a/tools/drv_status.py +++ b/tools/drv_status.py @@ -5,12 +5,14 @@ Example usage of the ODrive python library to monitor and control ODrive devices from __future__ import print_function -import odrive.core +import odrive.discovery import time import math # Find a connected ODrive (this will block until you connect one) -my_drive = odrive.core.find_any(consider_usb=True, consider_serial=False, printer=print) +print("Waiting for ODrive...") +my_drive = odrive.discovery.find_any() +print("connected") # Print DRV device regs for Motor 0 fault = my_drive.motor0.gate_driver.drv_fault diff --git a/tools/explore_odrive.py b/tools/explore_odrive.py index 9efaebb7..b6120a5c 100755 --- a/tools/explore_odrive.py +++ b/tools/explore_odrive.py @@ -3,10 +3,15 @@ Load an odrive object to play with in the IPython interactive shell. """ -import odrive.core import argparse import sys import platform +import threading +import odrive.discovery + +# Flush stdout by default +import functools +print = functools.partial(print, flush=True) # Check if IPython is installed try: @@ -49,24 +54,27 @@ CTRL_MODE_POSITION_CONTROL = 3 # Parse arguments -parser = argparse.ArgumentParser(description='Load an odrive object to play with in the IPython interactive shell.') +parser = argparse.ArgumentParser(description='Load an odrive object to play with in the IPython interactive shell.', + formatter_class=argparse.RawTextHelpFormatter) parser.add_argument("-v", "--verbose", action="store_true", help="print debug information") -group = parser.add_mutually_exclusive_group() -group.add_argument("-d", "--discover", metavar="CHANNELS", action="store", - help="Automatically discover ODrives. Takes a comma-separated list (without spaces) " - "to indicate which connection types should be considered. Possible values are " - "usb and serial. For example \"--discover usb,serial\" indicates " - "that USB and serial ports should be scanned for ODrives. " - "If none of the below options are specified, --discover usb is assumed.") -group.add_argument("-u", "--usb", metavar="BUS:DEVICE", action="store", - help="Specifies the USB port on which the device is connected. " - "For example \"001:014\" means bus 001, device 014. The numbers can be obtained " - "using `lsusb`.") -group.add_argument("-s", "--serial", metavar="PORT", action="store", - help="Specifies the serial port on which the device is connected. " - "For example \"/dev/ttyUSB0\". Use `ls /dev/tty*` to find your port name.") -parser.set_defaults(discover="usb") +parser.add_argument("-p", "--path", metavar="PATH", action="store", + help="The path(s) where ODrive(s) should be discovered.\n" + "By default the script will connect to any ODrive on USB.\n\n" + "To select a specific USB device:\n" + " --path usb:BUS:DEVICE\n" + "usbwhere BUS and DEVICE are the bus and device numbers as shown in `lsusb`.\n\n" + "To select a specific serial port:\n" + " --path serial:PATH\n" + "where PATH is the path of the serial port. For example \"/dev/ttyUSB0\".\n" + "You can use `ls /dev/tty*` to find the correct port.\n\n" + "You can combine USB and serial specs by separating them with a comma (no space!)\n" + "Example:\n" + " --path usb,serial:/dev/ttyUSB0\n" + "means \"discover any USB device or a serial device on /dev/ttyUSB0\"") +parser.add_argument("-s", "--serial-number", action="store", + help="The serial number of the device. If omitted, any device is accepted.\n") +parser.set_defaults(path="usb") args = parser.parse_args() if (args.verbose): @@ -74,41 +82,109 @@ if (args.verbose): else: printer = lambda x: None +COLOR_RED = '\x1b[91;1m' +COLOR_CYAN = '\x1b[96;1m' +COLOR_RESET = '\x1b[0m' + +interactive_variables = {} +discovered_devices = [] + +def did_discover_device(odrive): + """ + Handles the discovery of new devices by displaying a + message and making the device available to the interactive + console + """ + serial_number = odrive.serial_number if hasattr(odrive, 'serial_number') else "[unknown serial number]" + if serial_number in discovered_devices: + verb = "Reconnected" + index = discovered_devices.index(serial_number) + else: + verb = "Connected" + discovered_devices.append(serial_number) + index = len(discovered_devices) - 1 + interactive_name = "odrv" + str(index) + + # Subscribe to disappearance of the device + odrive.__dict__["__sealed__"] = False + odrive._did_disappear_callback = lambda: did_lose_device(interactive_name) + odrive.__sealed__ = True + + # Publish new ODrive to interactive console + interactive_variables[interactive_name] = odrive + globals()[interactive_name] = odrive # Add to globals so tab complete works + print_on_second_last_line(COLOR_CYAN + "{} to ODrive {:012X} as {}".format(verb, serial_number, interactive_name) + COLOR_RESET) + +def did_lose_device(interactive_name): + """ + Handles the disappearance of a device by displaying + a message. + """ + print_on_second_last_line(COLOR_RED + "Oh no {} disappeared".format(interactive_name) + COLOR_RESET) + # Connect to device -if not args.usb is None: - try: - bus = int(args.usb.split(":")[0]) - address = int(args.usb.split(":")[1]) - except (ValueError, IndexError): - print("the --usb argument must look something like this: \"001:014\"") - sys.exit(1) - try: - my_odrive = odrive.core.open_usb(bus, address, printer=printer) - except odrive.protocol.DeviceInitException as ex: - print(str(ex)) - sys.exit(1) -elif not args.serial is None: - my_odrive = odrive.core.open_serial(args.serial, printer=printer) -else: - print("Waiting for device...") - consider_usb = 'usb' in args.discover.split(',') - consider_serial = 'serial' in args.discover.split(',') - my_odrive = odrive.core.find_any(consider_usb, consider_serial, printer=printer) -print("Connected!") +printer("Waiting for device...") +app_shutdown_token = threading.Event() +odrive.discovery.find_all(args.path, args.serial_number, + did_discover_device, app_shutdown_token, + printer=printer) -print('') -print('ODRIVE EXPLORER') -print('') -print('You can now type "my_odrive." and press ') -print('This will present you with all the properties that you can reference') -print('') -print('For example: "my_odrive.motor0.encoder.pll_pos"') -print('will print the current encoder position on motor 0') -print('and "my_odrive.motor0.pos_setpoint = 10000"') -print('will send motor0 to 10000') -print('') +def print_help(): + print('') + print('ODRIVE EXPLORER') + print('') + print('Type "odrv0." and press ') + print('This will present you with all the properties that you can reference') + print('') + print('For example: "odrv0.motor0.encoder.pll_pos"') + print('will print the current encoder position on motor 0') + print('and "odrv0.motor0.pos_setpoint = 10000"') + print('will send motor0 to 10000') + print('') -# If IPython is installed, embed shell, otherwise drop into interactive stock python shell +def print_on_second_last_line(text, **kwargs): + """ + Prints a text on the second last line. + This can be used to print a message above the command + prompt. If the command prompt spans multiple lines, + there will be glitches. + """ + # Escape character sequence: + # ESC 7: store cursor position + # ESC 1A: move cursor up by one + # ESC 1S: scroll entire viewport by one + # ESC 1L: insert 1 line at cursor position + # (print text) + # ESC 8: restore old cursor position + kwargs['end'] = '' + kwargs['flush'] = True + print('\x1b7\x1b[1A\x1b[1S\x1b[1L' + text + '\x1b8', **kwargs) + + +embed_ipython = False + +# If IPython is installed, embed IPython shell, otherwise embed regular shell if embed_ipython: - IPython.embed() + IPython.embed() +else: + import code + import rlcompleter + import readline + readline.parse_and_bind("tab: complete") + console = code.InteractiveConsole(locals=interactive_variables) + console.locals["help"] = print_help + console.runcode('import sys') + console.runcode('superexcepthook = sys.excepthook') + console.runcode('def newexcepthook(ex_class,ex,trace):\n' + ' if ex_class.__module__ == "odrive.protocol" and ex_class.__name__ == "ChannelBrokenException":\n' + ' pass\n' + ' else:\n' + ' superexcepthook(ex_class,ex,trace)') + console.runcode('sys.excepthook=newexcepthook') + #print = print_on_second_last_line + console.interact(banner='ODrive control utility v0.4\n' + 'Please connect your ODrive.\n' + 'Type help() for help.') + +app_shutdown_token.set() diff --git a/tools/liveplotter.py b/tools/liveplotter.py index 1c4ba849..e67ca8cf 100755 --- a/tools/liveplotter.py +++ b/tools/liveplotter.py @@ -4,7 +4,7 @@ Liveplotter """ import time -import odrive.core +import odrive.discovery import matplotlib.pyplot as plt import numpy as np import threading @@ -13,7 +13,7 @@ data_rate = 100 plot_rate = 10 num_samples = 1000 -my_odrive = odrive.core.find_any() +my_odrive = odrive.discovery.find_any() plt.ion() global vals @@ -30,7 +30,7 @@ def fetch_data(): global vals global cancellation_token while not cancellation_token.is_set(): - vals.append(my_odrive.motor0.timing_log.TIMING_LOG_FOC_CURRENT) + vals.append(my_odrive.motor0.encoder.pll_pos) if len(vals) > num_samples: vals = vals[-num_samples:] time.sleep(1/data_rate) diff --git a/tools/odrive/core.py b/tools/odrive/core.py deleted file mode 100644 index bb2bd8e1..00000000 --- a/tools/odrive/core.py +++ /dev/null @@ -1,334 +0,0 @@ -""" -Provides functions for the discovery of ODrive devices -""" - -import sys -import time -import json -import usb.core -import usb.util -import serial -import serial.tools.list_ports -import odrive.util -import odrive.usbbulk_transport -import odrive.serial_transport -import re -import time -import os -import odrive.protocol -import itertools -import struct -import functools - -def noprint(x): - pass - - -class SimpleDeviceProperty(property): - """ - Used internally by dynamically created objects to translate - property assignments and fetches into endpoint operations on the - object's associated channel - """ - def __init__(self, channel, id, type, struct_format, can_read, can_write): - self._channel = channel - self._id = id - self._type = type - self._struct_format = struct_format - property.__init__(self, - self.fget if can_read else None, - self.fset if can_write else None) - - def fget(self, obj): - size = struct.calcsize(self._struct_format) - buffer = self._channel.remote_endpoint_operation(self._id, None, True, size) - return struct.unpack(self._struct_format, buffer)[0] - - def fset(self, obj, value): - value = self._type(value) - buffer = struct.pack(self._struct_format, value) - # TODO: Currenly we wait for an ack here. Settle on the default guarantee. - self._channel.remote_endpoint_operation(self._id, buffer, True, 0) - -def call_remote_function(channel, trigger_id, arg_properties, *args): - """ - Used internally by the dynamically created objects to translate - function calls into endpoint operations on the associated channel - """ - if (len(arg_properties) != len(args)): - raise TypeError("expected {} arguments but have {}".format(len(arg_properties), len(args))) - for i in range(len(args)): - arg_properties[i].fset(None, args[i]) - channel.remote_endpoint_operation(trigger_id, None, True, 0) - -def setattr_or_raise_if_undefined(self, name, value): - """ - If employed as an object's __setattr__ function, this function - makes sure that an assignment to an undefined attribute doesn't - create a new attribute but instead raises an exception - """ - # We can't use hasattr here because internally it fetches the property - # value, creating unnecessary bus traffic - if name in dir(self): - object.__setattr__(self, name, value) - else: - raise TypeError('Cannot set name %r on object of type %s' % ( - name, self.__class__.__name__)) - -def create_property(name, json_data, channel, printer): - """ - Dynamically creates a property based on a JSON definition - """ - name = name or "[anonymous]" - - type_str = json_data.get("type", None) - if type_str is None: - printer("property {} has no specified type".format(name)) - return None - - if type_str == "float": - property_type = float - struct_format = " Date: Sat, 17 Mar 2018 15:50:31 -0700 Subject: [PATCH 006/215] move liveplotter to odrive.utils, make explore_odrive work with IPython --- tools/explore_odrive.py | 181 ++++++++++++++++++++++------------------ tools/liveplotter.py | 43 ++-------- tools/odrive/utils.py | 142 +++++++++++++++++++++++++++++++ 3 files changed, 249 insertions(+), 117 deletions(-) create mode 100755 tools/odrive/utils.py diff --git a/tools/explore_odrive.py b/tools/explore_odrive.py index b6120a5c..fcf3b7d0 100755 --- a/tools/explore_odrive.py +++ b/tools/explore_odrive.py @@ -8,38 +8,12 @@ import sys import platform import threading import odrive.discovery +from odrive.utils import start_liveplotter # Flush stdout by default import functools print = functools.partial(print, flush=True) -# Check if IPython is installed -try: - import IPython - embed_ipython = True -except: - embed_ipython = False - - print("Warning: you don't have IPython installed.") - print("If you want to have an improved interactive console with pretty colors,") - print("you should install IPython\n") - - # Ensure interactive mode - if not bool(getattr(sys, 'ps1', sys.flags.interactive)): - print("You're not running in interactive mode. Run python -i explore_odrive.py") - print('') - sys.exit(1) - - # Enable tab complete if possible - try: - import readline - readline.parse_and_bind("tab: complete") - except: - sudo_prefix = "" if platform.system() == "Windows" else "sudo " - print("Warning: could not enable tab-complete. User experience will suffer.\n" - "Run `{}pip install readline` and then restart this script to fix this." - .format(sudo_prefix)) - # some enums described in the README # TODO: transmit as part of the JSON @@ -53,7 +27,7 @@ CTRL_MODE_VELOCITY_CONTROL = 2, CTRL_MODE_POSITION_CONTROL = 3 -# Parse arguments +## Parse arguments ## parser = argparse.ArgumentParser(description='Load an odrive object to play with in the IPython interactive shell.', formatter_class=argparse.RawTextHelpFormatter) parser.add_argument("-v", "--verbose", action="store_true", @@ -72,6 +46,10 @@ parser.add_argument("-p", "--path", metavar="PATH", action="store", "Example:\n" " --path usb,serial:/dev/ttyUSB0\n" "means \"discover any USB device or a serial device on /dev/ttyUSB0\"") +parser.add_argument("--no-ipython", action="store_true", + help="Use the regular Python shell\n" + "instead of the IPython shell,\n" + "even if IPython is installed\n") parser.add_argument("-s", "--serial-number", action="store", help="The serial number of the device. If omitted, any device is accepted.\n") parser.set_defaults(path="usb") @@ -82,11 +60,60 @@ if (args.verbose): else: printer = lambda x: None + +## Interactive console utils ## + COLOR_RED = '\x1b[91;1m' COLOR_CYAN = '\x1b[96;1m' COLOR_RESET = '\x1b[0m' +def print_on_second_last_line(text, **kwargs): + """ + Prints a text on the second last line. + This can be used to print a message above the command + prompt. If the command prompt spans multiple lines, + there will be glitches. + """ + # Escape character sequence: + # ESC 7: store cursor position + # ESC 1A: move cursor up by one + # ESC 1S: scroll entire viewport by one + # ESC 1L: insert 1 line at cursor position + # (print text) + # ESC 8: restore old cursor position + kwargs['end'] = '' + kwargs['flush'] = True + print('\x1b7\x1b[1A\x1b[1S\x1b[1L' + text + '\x1b8', **kwargs) + +def print_banner(): + print('ODrive control utility v0.4') + print('Please connect your ODrive.') + print('Type help() for help.') + +def print_help(): + print('') + if len(discovered_devices) == 0: + print('Connect your ODrive to {} and power it up.'.format(args.path)) + print('After that, the following message should appear:') + print(' "Connected to ODrive [serial number] as odrv0"') + print('') + print('Once the ODrive is connected, type "odrv0." and press ') + else: + print('Type "odrv0." and press ') + print('This will present you with all the properties that you can reference') + print('') + print('For example: "odrv0.motor0.encoder.pll_pos"') + print('will print the current encoder position on motor 0') + print('and "odrv0.motor0.pos_setpoint = 10000"') + print('will send motor0 to 10000') + print('') + interactive_variables = {} +interactive_variables["help"] = print_help + + +## Device discovery ## + discovered_devices = [] def did_discover_device(odrive): @@ -130,61 +157,55 @@ odrive.discovery.find_all(args.path, args.serial_number, printer=printer) -def print_help(): - print('') - print('ODRIVE EXPLORER') - print('') - print('Type "odrv0." and press ') - print('This will present you with all the properties that you can reference') - print('') - print('For example: "odrv0.motor0.encoder.pll_pos"') - print('will print the current encoder position on motor 0') - print('and "odrv0.motor0.pos_setpoint = 10000"') - print('will send motor0 to 10000') - print('') +## Launch interactive shell ## -def print_on_second_last_line(text, **kwargs): - """ - Prints a text on the second last line. - This can be used to print a message above the command - prompt. If the command prompt spans multiple lines, - there will be glitches. - """ - # Escape character sequence: - # ESC 7: store cursor position - # ESC 1A: move cursor up by one - # ESC 1S: scroll entire viewport by one - # ESC 1L: insert 1 line at cursor position - # (print text) - # ESC 8: restore old cursor position - kwargs['end'] = '' - kwargs['flush'] = True - print('\x1b7\x1b[1A\x1b[1S\x1b[1L' + text + '\x1b8', **kwargs) - - -embed_ipython = False +# Check if IPython is installed +if args.no_ipython: + use_ipython = False +else: + try: + import IPython + use_ipython = True + except: + print("Warning: you don't have IPython installed.") + print("If you want to have an improved interactive console with pretty colors,") + print("you should install IPython\n") + use_ipython = False # If IPython is installed, embed IPython shell, otherwise embed regular shell -if embed_ipython: - IPython.embed() +if use_ipython: + #interactive_variables["lalala"] = print_help + help = print_help # Override help function + console = IPython.terminal.embed.InteractiveShellEmbed(local_ns=interactive_variables, banner1='') + console.runcode = console.run_code # hack to make IPython look like the regular console + interact = console else: - import code - import rlcompleter - import readline - readline.parse_and_bind("tab: complete") - console = code.InteractiveConsole(locals=interactive_variables) - console.locals["help"] = print_help - console.runcode('import sys') - console.runcode('superexcepthook = sys.excepthook') - console.runcode('def newexcepthook(ex_class,ex,trace):\n' - ' if ex_class.__module__ == "odrive.protocol" and ex_class.__name__ == "ChannelBrokenException":\n' - ' pass\n' - ' else:\n' - ' superexcepthook(ex_class,ex,trace)') - console.runcode('sys.excepthook=newexcepthook') - #print = print_on_second_last_line - console.interact(banner='ODrive control utility v0.4\n' - 'Please connect your ODrive.\n' - 'Type help() for help.') + # Enable tab complete if possible + try: + import rlcompleter + import readline + readline.parse_and_bind("tab: complete") + except: + sudo_prefix = "" if platform.system() == "Windows" else "sudo " + print("Warning: could not enable tab-complete. User experience will suffer.\n" + "Run `{}pip install readline` and then restart this script to fix this." + .format(sudo_prefix)) + + import code + console = code.InteractiveConsole(locals=interactive_variables) + interact = lambda: console.interact(banner='') + +# install hook to hide ChannelBrokenException +console.runcode('import sys') +console.runcode('superexcepthook = sys.excepthook') +console.runcode('def newexcepthook(ex_class,ex,trace):\n' + ' if ex_class.__module__ + "." + ex_class.__name__ != "odrive.protocol.ChannelBrokenException":\n' + ' superexcepthook(ex_class,ex,trace)') +console.runcode('sys.excepthook=newexcepthook') + + +# Launch shell +print_banner() +interact() app_shutdown_token.set() diff --git a/tools/liveplotter.py b/tools/liveplotter.py index e67ca8cf..95c50c24 100755 --- a/tools/liveplotter.py +++ b/tools/liveplotter.py @@ -4,10 +4,9 @@ Liveplotter """ import time -import odrive.discovery -import matplotlib.pyplot as plt -import numpy as np import threading +import odrive.discovery +from odrive.utils import start_liveplotter data_rate = 100 plot_rate = 10 @@ -15,38 +14,8 @@ num_samples = 1000 my_odrive = odrive.discovery.find_any() -plt.ion() -global vals -vals = [] +# If you want to plot different values, change them here. +# You can plot any number of values concurrently. +start_liveplotter(lambda: [my_odrive.motor0.encoder.pll_pos, + my_odrive.motor1.encoder.pll_pos]) -# Make sure the script terminates when the user closes the plotter -cancellation_token = threading.Event() -def handle_close(evt): - cancellation_token.set() -fig = plt.figure() -fig.canvas.mpl_connect('close_event', handle_close) - -def fetch_data(): - global vals - global cancellation_token - while not cancellation_token.is_set(): - vals.append(my_odrive.motor0.encoder.pll_pos) - if len(vals) > num_samples: - vals = vals[-num_samples:] - time.sleep(1/data_rate) - -# TODO: use animation for better UI performance, see: -# https://matplotlib.org/examples/animation/simple_anim.html -def plot_data(): - global vals - global cancellation_token - while not cancellation_token.is_set(): - plt.clf() - plt.plot(vals) - #time.sleep(1/plot_rate) - fig.canvas.flush_events() - -fetch_thread = threading.Thread(target=fetch_data, daemon=True) -fetch_thread.start() - -plot_data() diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py new file mode 100755 index 00000000..f1fb779b --- /dev/null +++ b/tools/odrive/utils.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +""" +Liveplotter +""" + +import sys +import time +import threading + +data_rate = 100 +plot_rate = 10 +num_samples = 1000 + +def start_liveplotter(get_var_callback): + """ + Starts a liveplotter. + The variable that is plotted is retrieved from get_var_callback. + This function returns immediately and the liveplotter quits when + the user closes it. + """ + + import matplotlib.pyplot as plt + import numpy as np + + cancellation_token = threading.Event() + + global vals + vals = [] + def fetch_data(): + global vals + while not cancellation_token.is_set(): + try: + data = get_var_callback() + except Exception as ex: + print(str(ex)) + time.sleep(1) + continue + vals.append(data) + if len(vals) > num_samples: + vals = vals[-num_samples:] + time.sleep(1/data_rate) + + # TODO: use animation for better UI performance, see: + # https://matplotlib.org/examples/animation/simple_anim.html + def plot_data(): + global vals + + plt.ion() + + # Make sure the script terminates when the user closes the plotter + def did_close(evt): + cancellation_token.set() + fig = plt.figure() + fig.canvas.mpl_connect('close_event', did_close) + + while not cancellation_token.is_set(): + plt.clf() + plt.plot(vals) + #time.sleep(1/plot_rate) + fig.canvas.flush_events() + + threading.Thread(target=fetch_data).start() + threading.Thread(target=plot_data).start() + #plot_data() + +## Exceptions ## + +class TimeoutException(Exception): + pass + +## Threading utils ## + +class Event(): + """ + Alternative to threading.Event(), enhanced by the subscribe() function + that the original fails to provide. + """ + def __init__(self): + self._evt = threading.Event() + self._subscribers = [] + self._mutex = threading.Lock() + + def is_set(self): + return self._evt.is_set() + + def set(self): + """ + Sets the event and invokes all subscribers if the event was + not already set + """ + self._mutex.acquire() + try: + if not self._evt.is_set(): + self._evt.set() + for s in self._subscribers: + s() + finally: + self._mutex.release() + + def subscribe(self, handler): + """ + Invokes the specified handler exactly once as soon as the + specified event is set. If the event is already set, the + handler is invoked immediately. + Returns a function that can be invoked to unsubscribe. + """ + self._mutex.acquire() + try: + self._subscribers.append(handler) + if self._evt.is_set(): + handler() + finally: + self._mutex.release() + return lambda: self.unsubscribe(handler) + + def unsubscribe(self, handler): + self._mutex.acquire() + try: + self._subscribers.pop(self._subscribers.index(handler)) + finally: + self._mutex.release() + + def wait(self, timeout=None): + return self._evt.wait(timeout=timeout) + +def wait_any(*events, timeout=None): + """ + Blocks until any of the specified events are triggered. + Returns the number of the event that was triggerd or raises + a TimeoutException + """ + or_event = threading.Event() + unsubscribe_functions = [] + for event in events: + unsubscribe_functions.append(event.subscribe(lambda: or_event.set())) + or_event.wait(timeout=timeout) + for unsubscribe_function in unsubscribe_functions: + unsubscribe_function() + for i in range(len(events)): + if events[i].is_set(): + return i + raise TimeoutException() From f4bded078fd5a3772a8d575db5191a9187945bf8 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sat, 17 Mar 2018 18:27:24 -0700 Subject: [PATCH 007/215] make remote_endpoint_operation thread-safe --- tools/odrive/discovery.py | 51 +++++++++------- tools/odrive/protocol.py | 102 +++++++++++++++++++++---------- tools/odrive/serial_transport.py | 3 +- 3 files changed, 102 insertions(+), 54 deletions(-) diff --git a/tools/odrive/discovery.py b/tools/odrive/discovery.py index 745a9e92..7822950a 100644 --- a/tools/odrive/discovery.py +++ b/tools/odrive/discovery.py @@ -7,6 +7,7 @@ import json import time import threading import odrive.protocol +import odrive.utils import odrive.remote_object import odrive.usbbulk_transport import odrive.serial_transport @@ -35,29 +36,35 @@ def find_all(path, serial_number, This queries the endpoint 0 on that channel to gain information about the interface, which is then used to init the corresponding object. """ - printer("Connecting to device on " + channel._name) try: - json_bytes = channel.remote_endpoint_read_buffer(0) - except (odrive.protocol.TimeoutException, odrive.protocol.ChannelBrokenException): - raise odrive.protocol.DeviceInitException("no response - probably incompatible") - json_crc16 = odrive.protocol.calc_crc16(odrive.protocol.PROTOCOL_VERSION, json_bytes) - channel._interface_definition_crc = json_crc16 - try: - json_string = json_bytes.decode("ascii") - except UnicodeDecodeError: - raise odrive.protocol.DeviceInitException("device responded on endpoint 0 with something that is not ASCII") - printer("JSON: " + json_string) - try: - json_data = json.loads(json_string) - except json.decoder.JSONDecodeError as error: - raise odrive.protocol.DeviceInitException("device responded on endpoint 0 with something that is not JSON: " + str(error)) - json_data = {"name": "odrive", "members": json_data} - obj = odrive.remote_object.RemoteObject(json_data, None, channel, None, printer) - device_serial_number = serial_number if hasattr(obj, 'serial_number') else "[unknown serial number]" - if serial_number != None and device_serial_number != serial_number: - printer("Ignoring device with serial number {}".format(device_serial_number)) - return - did_discover_object_callback(obj) + printer("Connecting to device on " + channel._name) + try: + json_bytes = channel.remote_endpoint_read_buffer(0) + except (odrive.utils.TimeoutException, odrive.protocol.ChannelBrokenException): + printer("no response - probably incompatible") + return + json_crc16 = odrive.protocol.calc_crc16(odrive.protocol.PROTOCOL_VERSION, json_bytes) + channel._interface_definition_crc = json_crc16 + try: + json_string = json_bytes.decode("ascii") + except UnicodeDecodeError: + printer("device responded on endpoint 0 with something that is not ASCII") + return + printer("JSON: " + json_string) + try: + json_data = json.loads(json_string) + except json.decoder.JSONDecodeError as error: + printer("device responded on endpoint 0 with something that is not JSON: " + str(error)) + return + json_data = {"name": "odrive", "members": json_data} + obj = odrive.remote_object.RemoteObject(json_data, None, channel, None, printer) + device_serial_number = serial_number if hasattr(obj, 'serial_number') else "[unknown serial number]" + if serial_number != None and device_serial_number != serial_number: + printer("Ignoring device with serial number {}".format(device_serial_number)) + return + did_discover_object_callback(obj) + except Exception as ex: + printer("Unexpected exception after discovering channel: " + str(ex)) # For each connection type, kick off an appropriate discovery loop for search_spec in path.split(','): diff --git a/tools/odrive/protocol.py b/tools/odrive/protocol.py index 4878fadc..a6c326eb 100644 --- a/tools/odrive/protocol.py +++ b/tools/odrive/protocol.py @@ -3,6 +3,10 @@ import time import struct import sys +import threading +import odrive.utils +from odrive.utils import wait_any +from odrive.utils import Event import abc @@ -63,9 +67,6 @@ def calc_crc16(remainder, value): #print(hex(calc_crc16(0xfeef, [1, 2, 3, 4, 5, 0x10, 0x13, 0x37]))) -class TimeoutException(Exception): - pass - class DeviceInitException(Exception): pass @@ -204,11 +205,14 @@ class Channel(PacketSink): _outbound_seq_no = 0 _interface_definition_crc = 0 _expected_acks = {} + _responses = {} # Choose these parameters to be sensible for a specific transport layer _resend_timeout = 0.1 # [s] _send_attempts = 5 + _channel_broken = Event() + def __init__(self, name, input, output): """ Params: @@ -220,6 +224,33 @@ class Channel(PacketSink): self._name = name self._input = input self._output = output + self._my_lock = threading.Lock() + self.start_receiver_thread(Event()) # TODO: use app_shutdown_token + + def start_receiver_thread(self, cancellation_token): + """ + Starts the receiver thread that processes incoming messages. + The thread quits as soon as the channel enters a broken state. + """ + def receiver_thread(): + try: + while (not cancellation_token.is_set()) and (not self._channel_broken.is_set()): + # Set an arbitrary deadline because the get_packet function + # currently doesn't support a cancellation_token + deadline = time.monotonic() + 1.0 + try: + response = self._input.get_packet(deadline) + except odrive.utils.TimeoutException: + continue # try again + except ChannelDamagedException: + continue # try again + # Process response + # This should not throw an exception, otherwise the channel breaks + self.process_packet(response) + print("receiver thread is exiting") + finally: + self._channel_broken.set() + threading.Thread(target=receiver_thread, daemon=True).start() def remote_endpoint_operation(self, endpoint_id, input, expect_ack, output_length): if input is None: @@ -230,9 +261,13 @@ class Channel(PacketSink): if (expect_ack): endpoint_id |= 0x8000 - self._outbound_seq_no = ((self._outbound_seq_no + 1) & 0x7fff) - self._outbound_seq_no |= 0x80 # FIXME: we hardwire one bit of the seq-no to 1 to avoid conflicts with the legacy protocol - seq_no = self._outbound_seq_no + self._my_lock.acquire() + try: + self._outbound_seq_no = ((self._outbound_seq_no + 1) & 0x7fff) + seq_no = self._outbound_seq_no + finally: + self._my_lock.release() + seq_no |= 0x80 # FIXME: we hardwire one bit of the seq-no to 1 to avoid conflicts with the legacy protocol packet = struct.pack(' Date: Sat, 17 Mar 2018 19:24:08 -0700 Subject: [PATCH 008/215] robustify connect and disconnect logic --- tools/explore_odrive.py | 11 ++++---- tools/odrive/discovery.py | 7 +++-- tools/odrive/protocol.py | 25 ++++++++-------- tools/odrive/remote_object.py | 47 +++++++++---------------------- tools/odrive/serial_transport.py | 2 +- tools/odrive/usbbulk_transport.py | 18 ++++++------ tools/odrive/utils.py | 1 - 7 files changed, 47 insertions(+), 64 deletions(-) diff --git a/tools/explore_odrive.py b/tools/explore_odrive.py index fcf3b7d0..ccc252d4 100755 --- a/tools/explore_odrive.py +++ b/tools/explore_odrive.py @@ -132,22 +132,21 @@ def did_discover_device(odrive): index = len(discovered_devices) - 1 interactive_name = "odrv" + str(index) - # Subscribe to disappearance of the device - odrive.__dict__["__sealed__"] = False - odrive._did_disappear_callback = lambda: did_lose_device(interactive_name) - odrive.__sealed__ = True - # Publish new ODrive to interactive console interactive_variables[interactive_name] = odrive globals()[interactive_name] = odrive # Add to globals so tab complete works print_on_second_last_line(COLOR_CYAN + "{} to ODrive {:012X} as {}".format(verb, serial_number, interactive_name) + COLOR_RESET) + # Subscribe to disappearance of the device + odrive.__channel__._channel_broken.subscribe(lambda: did_lose_device(interactive_name)) + def did_lose_device(interactive_name): """ Handles the disappearance of a device by displaying a message. """ - print_on_second_last_line(COLOR_RED + "Oh no {} disappeared".format(interactive_name) + COLOR_RESET) + if not app_shutdown_token.is_set(): + print_on_second_last_line(COLOR_RED + "Oh no {} disappeared".format(interactive_name) + COLOR_RESET) # Connect to device printer("Waiting for device...") diff --git a/tools/odrive/discovery.py b/tools/odrive/discovery.py index 7822950a..f31f0a2d 100644 --- a/tools/odrive/discovery.py +++ b/tools/odrive/discovery.py @@ -6,6 +6,7 @@ import sys import json import time import threading +import traceback import odrive.protocol import odrive.utils import odrive.remote_object @@ -57,14 +58,14 @@ def find_all(path, serial_number, printer("device responded on endpoint 0 with something that is not JSON: " + str(error)) return json_data = {"name": "odrive", "members": json_data} - obj = odrive.remote_object.RemoteObject(json_data, None, channel, None, printer) + obj = odrive.remote_object.RemoteObject(json_data, None, channel, printer) device_serial_number = serial_number if hasattr(obj, 'serial_number') else "[unknown serial number]" if serial_number != None and device_serial_number != serial_number: printer("Ignoring device with serial number {}".format(device_serial_number)) return did_discover_object_callback(obj) - except Exception as ex: - printer("Unexpected exception after discovering channel: " + str(ex)) + except Exception: + printer("Unexpected exception after discovering channel: " + traceback.format_exc()) # For each connection type, kick off an appropriate discovery loop for search_spec in path.split(','): diff --git a/tools/odrive/protocol.py b/tools/odrive/protocol.py index a6c326eb..413fca58 100644 --- a/tools/odrive/protocol.py +++ b/tools/odrive/protocol.py @@ -4,6 +4,7 @@ import time import struct import sys import threading +import traceback import odrive.utils from odrive.utils import wait_any from odrive.utils import Event @@ -106,11 +107,10 @@ class PacketSink(ABC): class StreamToPacketConverter(StreamSink): - _header = [] - _packet = [] - _packet_length = 0 - def __init__(self, output): + self._header = [] + self._packet = [] + self._packet_length = 0 self._output = output def process_bytes(self, bytes): @@ -202,18 +202,11 @@ class PacketFromStreamConverter(PacketSource): class Channel(PacketSink): - _outbound_seq_no = 0 - _interface_definition_crc = 0 - _expected_acks = {} - _responses = {} - # Choose these parameters to be sensible for a specific transport layer _resend_timeout = 0.1 # [s] _send_attempts = 5 - _channel_broken = Event() - - def __init__(self, name, input, output): + def __init__(self, name, input, output, printer): """ Params: input: A PacketSource where this channel will source packets from on @@ -224,7 +217,13 @@ class Channel(PacketSink): self._name = name self._input = input self._output = output + self._printer = printer + self._outbound_seq_no = 0 + self._interface_definition_crc = 0 + self._expected_acks = {} + self._responses = {} self._my_lock = threading.Lock() + self._channel_broken = Event() self.start_receiver_thread(Event()) # TODO: use app_shutdown_token def start_receiver_thread(self, cancellation_token): @@ -248,6 +247,8 @@ class Channel(PacketSink): # This should not throw an exception, otherwise the channel breaks self.process_packet(response) print("receiver thread is exiting") + except Exception: + self._printer("receiver thread is exiting: " + traceback.format_exc()) finally: self._channel_broken.set() threading.Thread(target=receiver_thread, daemon=True).start() diff --git a/tools/odrive/remote_object.py b/tools/odrive/remote_object.py index 3c051620..58da44ab 100644 --- a/tools/odrive/remote_object.py +++ b/tools/odrive/remote_object.py @@ -16,17 +16,13 @@ import odrive.protocol class ObjectDefinitionError(Exception): pass -class RemoteProperty(property): +class RemoteProperty(): """ Used internally by dynamically created objects to translate property assignments and fetches into endpoint operations on the object's associated channel """ def __init__(self, json_data, parent): - property.__init__(self, - lambda prop, obj: self.get_value(prop), - lambda prop, obj, val: self.set_value(prop, val)) - self._parent = parent id_str = json_data.get("id", None) if id_str is None: @@ -39,7 +35,7 @@ class RemoteProperty(property): type_str = json_data.get("type", None) if type_str is None: - raise ObjectDefinitionError("unspecified type".format(name)) + raise ObjectDefinitionError("unspecified type") if type_str == "float": self._property_type = float @@ -116,31 +112,32 @@ class RemoteObject(object): """ Object with functions and properties that map to remote endpoints """ - _remote_attributes = {} - __sealed__ = False - - def __init__(self, json_data, parent, channel, did_disappear_callback, printer): + def __init__(self, json_data, parent, channel, printer): """ Creates an object that implements the specified JSON type description by communicating over the provided channel """ + # Directly write to __dict__ to avoid calling __setattr__ too early + object.__getattribute__(self, "__dict__")["_remote_attributes"] = {} + object.__getattribute__(self, "__dict__")["__sealed__"] = False + # Assign once more to make linter happy self._remote_attributes = {} self.__sealed__ = False + self.__channel__ = channel self.__parent__ = parent - self._did_disappear_callback = did_disappear_callback # Build attribute list from JSON for member_json in json_data.get("members", []): member_name = member_json.get("name", None) if member_name is None: - printer("ignoring unnamed attribute in {}".format(namespace)) + printer("ignoring unnamed attribute") continue try: type_str = member_json.get("type", None) if type_str == "object": - attribute = RemoteObject(member_json, self, channel, self._did_disappear, printer) + attribute = RemoteObject(member_json, self, channel, printer) elif type_str == "function": attribute = RemoteFunction(member_json, self) elif type_str != None: @@ -157,6 +154,7 @@ class RemoteObject(object): # Ensure that from here on out assignments to undefined attributes # raise an exception self.__sealed__ = True + channel._channel_broken.subscribe(self._tear_down) def __str__(self): return str(dir(self)) # TODO: improve print output @@ -164,17 +162,10 @@ class RemoteObject(object): return self.__str__() def __getattribute__(self, name): - #print("get attr " + name) - #d = object.__getattribute__(self, "__dict__") - #attr = d.get("_remote_attributes", {}).get(name, None) attr = object.__getattribute__(self, "_remote_attributes").get(name, None) if isinstance(attr, RemoteProperty): if attr._can_read: - try: - return attr.get_value() - except odrive.protocol.ChannelBrokenException: - self._did_disappear() - raise + return attr.get_value() else: raise Exception("Cannot read from property {}".format(name)) elif attr != None: @@ -184,15 +175,10 @@ class RemoteObject(object): #raise AttributeError("Attribute {} not found".format(name)) def __setattr__(self, name, value): - #print("set attr " + name) attr = object.__getattribute__(self, "_remote_attributes").get(name, None) if isinstance(attr, RemoteProperty): if attr._can_write: - try: - attr.set_value(value) - except odrive.protocol.ChannelBrokenException: - self._did_disappear() - raise + attr.set_value(value) else: raise Exception("Cannot write to property {}".format(name)) elif not object.__getattribute__(self, "__sealed__") or name in object.__getattribute__(self, "__dict__"): @@ -200,13 +186,8 @@ class RemoteObject(object): else: raise AttributeError("Attribute {} not found".format(name)) - def _did_disappear(self): + def _tear_down(self): # Clear all remote members for k in self._remote_attributes.keys(): self.__dict__.pop(k) self._remote_attributes = {} - - # Call hook - if self._did_disappear_callback: - self._did_disappear_callback() - diff --git a/tools/odrive/serial_transport.py b/tools/odrive/serial_transport.py index e4621f9e..cfb9168e 100644 --- a/tools/odrive/serial_transport.py +++ b/tools/odrive/serial_transport.py @@ -77,7 +77,7 @@ def discover_channels(path, serial_number, callback, cancellation_token, printer output_stream = odrive.protocol.PacketToStreamConverter(serial_device) channel = odrive.protocol.Channel( "serial port {}@{}".format(port_name, ODRIVE_BAUDRATE), - input_stream, output_stream) + input_stream, output_stream, printer) channel.serial_device = serial_device callback(channel) known_devices.append(port_name) diff --git a/tools/odrive/usbbulk_transport.py b/tools/odrive/usbbulk_transport.py index 8d0f1a19..2a7075b8 100644 --- a/tools/odrive/usbbulk_transport.py +++ b/tools/odrive/usbbulk_transport.py @@ -29,16 +29,12 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) for cfg in self.dev: string += "ConfigurationValue {0}\n".format(cfg.bConfigurationValue) for intf in cfg: - string += "\tInterfaceNumber {0},{0}\n".format(intf.bInterfaceNumber, intf.bAlternateSetting) + string += "\tInterfaceNumber {0},{1}\n".format(intf.bInterfaceNumber, intf.bAlternateSetting) for ep in intf: string += "\t\tEndpointAddress {0}\n".format(ep.bEndpointAddress) return string def init(self): - # Resetting device to start init from a known state - # self.dev.reset() - # time.sleep(1) - # detach kernel driver try: if self.dev.is_kernel_driver_active(1): self.dev.detach_kernel_driver(1) @@ -166,13 +162,19 @@ def discover_channels(path, serial_number, callback, cancellation_token, printer bulk_device.init() channel = odrive.protocol.Channel( "USB device bus {} device {}".format(usb_device.bus, usb_device.address), - bulk_device, bulk_device) + bulk_device, bulk_device, printer) channel.usb_device = usb_device # for debugging only except usb.core.USBError as ex: if ex.errno == 13: printer("USB device access denied. Did you set up your udev rules correctly?") continue - raise - callback(channel) + elif ex.errno == 16: + printer("USB device busy. I'll reset it and try again.") + usb_device.reset() + continue + else: + printer("USB device init failed. Ignoring this device") + else: + callback(channel) known_devices.append((usb_device.bus, usb_device.address)) time.sleep(1) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index f1fb779b..be19adcd 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -20,7 +20,6 @@ def start_liveplotter(get_var_callback): """ import matplotlib.pyplot as plt - import numpy as np cancellation_token = threading.Event() From 1386ff21202ca1a7f093233421840ebf8fc96317 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sat, 17 Mar 2018 19:50:27 -0700 Subject: [PATCH 009/215] update changelog --- Firmware/CHANGELOG.md | 7 +++++++ Firmware/README.md | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Firmware/CHANGELOG.md b/Firmware/CHANGELOG.md index b78e3f21..8e46007c 100644 --- a/Firmware/CHANGELOG.md +++ b/Firmware/CHANGELOG.md @@ -11,6 +11,13 @@ Please add a note of your changes below this heading if you make a Pull Request. * Update CubeMX generated STM platform code to version 1.19.0 * Remove `UUID_0`, `UUID_1` and `UUID_2` from USB protocol. Use `serial_number` instead. * Freertos memory pool (task stacks, etc) now uses Core Coupled Memory. +* Refactor python tools + * `explore_odrive.py` now supports controlling multiple ODrives concurrently (`odrv0`, `odrv1`, ...) + * No need to restart `explore_odrive.py` when devices get disconnected and reconnected + * The command line arguments of `explore_odrive.py` have changed. See `explore_odrive.py --help` for more details. + * ODrive accesses from within python tools are now thread-safe + * Liveplotter does no longer steal focus and closes as expected + * (experimental: start liveplotter from `explore_odrive.py` by typing `start_liveplotter(lambda: odrv0.motor0.encoder.encoder_state)`) ### Fixed * malloc now fails if we run out of memory (before it would always succeed even if we are out of ram...) diff --git a/Firmware/README.md b/Firmware/README.md index 540265d6..11f9445f 100644 --- a/Firmware/README.md +++ b/Firmware/README.md @@ -167,7 +167,7 @@ pip install pyusb pyserial 6. Open the bash prompt in the `ODrive/tools/` folder. 7. Run `python3 demo.py` or `python3 explore_odrive.py`. - `demo.py` is a very simple script which will make motor 0 turn back and forth. Use this as an example if you want to control the ODrive yourself programatically. -- `explore_odrive.py` drops you into an interactive python shell where you can explore and edit the parameters that are available on your device. For instance `my_odrive.motor0.pos_setpoint = 10000` makes motor0 move to position 10000. To connect over serial instead of USB run `./tools/explore_odrive.py --discover serial`. +- `explore_odrive.py` drops you into an interactive python shell where you can explore and edit the parameters that are available on your device. For instance `my_odrive.motor0.pos_setpoint = 10000` makes motor0 move to position 10000. To connect over serial instead of USB run `./tools/explore_odrive.py --path serial`. ### From Arduino [See ODrive Arduino Library](https://github.com/madcowswe/ODriveArduino) From a2413e3469b3af1fd74522ac465d448ac83700c7 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sat, 17 Mar 2018 20:29:57 -0700 Subject: [PATCH 010/215] robustify connect/reconnect via serial connection --- tools/odrive/serial_transport.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/tools/odrive/serial_transport.py b/tools/odrive/serial_transport.py index cfb9168e..53ca6850 100644 --- a/tools/odrive/serial_transport.py +++ b/tools/odrive/serial_transport.py @@ -39,6 +39,9 @@ class SerialStreamTransport(odrive.protocol.StreamSource, odrive.protocol.Stream raise odrive.utils.TimeoutException("expected {} bytes but got only {}", n_bytes, len(result)) return result + def close(self): + self._dev.close() + def find_dev_serial_ports(): try: @@ -68,17 +71,28 @@ def discover_channels(path, serial_number, callback, cancellation_token, printer return False return bool(re.match(regex, port_name)) + def did_disconnect(port_name, device): + device.close() + # TODO: yes there is a race condition here in case you wonder. + known_devices.pop(known_devices.index(port_name)) + while not cancellation_token.is_set(): all_ports = find_pyserial_ports() + find_dev_serial_ports() new_ports = filter(device_matcher, all_ports) for port_name in new_ports: - serial_device = SerialStreamTransport(port_name, ODRIVE_BAUDRATE) - input_stream = odrive.protocol.PacketFromStreamConverter(serial_device) - output_stream = odrive.protocol.PacketToStreamConverter(serial_device) - channel = odrive.protocol.Channel( - "serial port {}@{}".format(port_name, ODRIVE_BAUDRATE), - input_stream, output_stream, printer) - channel.serial_device = serial_device - callback(channel) - known_devices.append(port_name) + try: + serial_device = SerialStreamTransport(port_name, ODRIVE_BAUDRATE) + input_stream = odrive.protocol.PacketFromStreamConverter(serial_device) + output_stream = odrive.protocol.PacketToStreamConverter(serial_device) + channel = odrive.protocol.Channel( + "serial port {}@{}".format(port_name, ODRIVE_BAUDRATE), + input_stream, output_stream, printer) + channel.serial_device = serial_device + except serial.serialutil.SerialException: + printer("Serial device init failed. Ignoring this port") + known_devices.append(port_name) + else: + known_devices.append(port_name) + channel._channel_broken.subscribe(lambda: did_disconnect(port_name, serial_device)) + callback(channel) time.sleep(1) From 4404e0aa74449d6a6910077f0340c194cc002aa1 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sat, 17 Mar 2018 21:14:22 -0700 Subject: [PATCH 011/215] increase readability --- tools/odrive/usbbulk_transport.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/odrive/usbbulk_transport.py b/tools/odrive/usbbulk_transport.py index 2a7075b8..f513789f 100644 --- a/tools/odrive/usbbulk_transport.py +++ b/tools/odrive/usbbulk_transport.py @@ -174,7 +174,8 @@ def discover_channels(path, serial_number, callback, cancellation_token, printer continue else: printer("USB device init failed. Ignoring this device") + known_devices.append((usb_device.bus, usb_device.address)) else: + known_devices.append((usb_device.bus, usb_device.address)) callback(channel) - known_devices.append((usb_device.bus, usb_device.address)) time.sleep(1) From a2378dbf79f9ef6ec6ab2760ccdefeb4f017d7da Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 23 Mar 2018 16:53:18 -0700 Subject: [PATCH 012/215] make encoder index search and encoder offset calibration independent These two activities are now separate states of the axis state machine. Each of them can be invoked independently at any time (provided the motor is calibrated). --- Firmware/MotorControl/axis.cpp | 20 ++++-- Firmware/MotorControl/axis.hpp | 46 ++++++++------ Firmware/MotorControl/encoder.cpp | 102 +++++++++++++++++------------- Firmware/MotorControl/encoder.hpp | 20 ++++-- Firmware/MotorControl/motor.hpp | 5 +- 5 files changed, 114 insertions(+), 79 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index e186e864..d6c718e4 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -214,8 +214,10 @@ void Axis::run_state_machine_loop() { if (requested_state_ == AXIS_STATE_STARTUP_SEQUENCE) { if (config_.startup_motor_calibration) task_chain_[pos++] = AXIS_STATE_MOTOR_CALIBRATION; - if (config_.startup_encoder_calibration) - task_chain_[pos++] = AXIS_STATE_ENCODER_CALIBRATION; + if (config_.startup_encoder_index_search && encoder_.config_.use_index) + task_chain_[pos++] = AXIS_STATE_ENCODER_INDEX_SEARCH; + if (config_.startup_encoder_offset_calibration) + task_chain_[pos++] = AXIS_STATE_ENCODER_OFFSET_CALIBRATION; if (config_.startup_closed_loop_control) task_chain_[pos++] = AXIS_STATE_CLOSED_LOOP_CONTROL; else if (config_.startup_sensorless_control) @@ -223,7 +225,9 @@ void Axis::run_state_machine_loop() { task_chain_[pos++] = AXIS_STATE_IDLE; } else if (requested_state_ == AXIS_STATE_FULL_CALIBRATION_SEQUENCE) { task_chain_[pos++] = AXIS_STATE_MOTOR_CALIBRATION; - task_chain_[pos++] = AXIS_STATE_ENCODER_CALIBRATION; + if (encoder_.config_.use_index) + task_chain_[pos++] = AXIS_STATE_ENCODER_INDEX_SEARCH; + task_chain_[pos++] = AXIS_STATE_ENCODER_OFFSET_CALIBRATION; task_chain_[pos++] = AXIS_STATE_IDLE; } else if (requested_state_ != AXIS_STATE_UNDEFINED) { task_chain_[pos++] = requested_state_; @@ -239,7 +243,7 @@ void Axis::run_state_machine_loop() { // Validate the state before running it if (current_state_ > AXIS_STATE_MOTOR_CALIBRATION && !motor_.is_calibrated_) current_state_ = AXIS_STATE_UNDEFINED; - if (current_state_ > AXIS_STATE_ENCODER_CALIBRATION && !encoder_.is_calibrated_) + if (current_state_ > AXIS_STATE_ENCODER_OFFSET_CALIBRATION && !encoder_.is_ready_) current_state_ = AXIS_STATE_UNDEFINED; // Run the specified state @@ -250,8 +254,12 @@ void Axis::run_state_machine_loop() { status = motor_.run_calibration(); break; - case AXIS_STATE_ENCODER_CALIBRATION: - status = encoder_.run_calibration(); + case AXIS_STATE_ENCODER_INDEX_SEARCH: + status = encoder_.run_index_search(); + break; + + case AXIS_STATE_ENCODER_OFFSET_CALIBRATION: + status = encoder_.run_offset_calibration(); break; case AXIS_STATE_SENSORLESS_CONTROL: diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index d4feadbd..65ef9d97 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -8,19 +8,22 @@ // Warning: Do not reorder these enum values. // The state machine uses ">" comparision on them. enum AxisState_t { - AXIS_STATE_UNDEFINED, //Instance->CNT = count; pll_pos_ = (float)count; @@ -53,14 +59,59 @@ void Encoder::set_count(int32_t count) { } +// @brief Slowly turns the motor in one direction until the +// encoder index is found. // TODO: Do the scan with current, not voltage! -// TODO: add check_timing -bool Encoder::calib_enc_offset(float voltage_magnitude) { +bool Encoder::run_index_search() { + float voltage_magnitude; + if (axis_->motor_.config_.motor_type == MOTOR_TYPE_HIGH_CURRENT) + voltage_magnitude = axis_->motor_.config_.calibration_current * axis_->motor_.config_.phase_resistance; + else if (axis_->motor_.config_.motor_type == MOTOR_TYPE_GIMBAL) + voltage_magnitude = axis_->motor_.config_.calibration_current; + else + return false; + + float omega = (float)(axis_->motor_.config_.direction) * config_.idx_search_speed; + + index_found_ = false; + float phase = 0.0f; + axis_->run_control_loop([&](){ + phase = wrap_pm_pi(phase + omega * current_meas_period); + + float v_alpha = voltage_magnitude * arm_cos_f32(phase); + float v_beta = voltage_magnitude * arm_sin_f32(phase); + axis_->motor_.enqueue_voltage_timings(v_alpha, v_beta); + axis_->motor_.log_timing(Motor::TIMING_LOG_IDX_SEARCH); + + // continue until the index is found + return !index_found_; + }); + return axis_->error_ != Axis::ERROR_NO_ERROR; +} + +// @brief Turns the motor in one direction for a bit and then in the other +// direction in order to find the offset between the electrical phase 0 +// and the encoder state 0. +// TODO: Do the scan with current, not voltage! +bool Encoder::run_offset_calibration() { static const float start_lock_duration = 1.0f; static const float scan_omega = 4.0f * M_PI; static const float scan_distance = 16.0f * M_PI; static const int num_steps = scan_distance / scan_omega * current_meas_hz; + // Temporarily disable index search so it doesn't mess + // with the offset calibration + bool old_use_index = config_.use_index; + config_.use_index = true; + + float voltage_magnitude; + if (axis_->motor_.config_.motor_type == MOTOR_TYPE_HIGH_CURRENT) + voltage_magnitude = axis_->motor_.config_.calibration_current * axis_->motor_.config_.phase_resistance; + else if (axis_->motor_.config_.motor_type == MOTOR_TYPE_GIMBAL) + voltage_magnitude = axis_->motor_.config_.calibration_current; + else + return false; + // go to motor zero phase for start_lock_duration to get ready to scan int i = 0; axis_->run_control_loop([&](){ @@ -128,46 +179,9 @@ bool Encoder::calib_enc_offset(float voltage_magnitude) { if (axis_->error_ != Axis::ERROR_NO_ERROR) return false; - int offset = encvaluesum / (num_steps * 2); - config_.offset = offset; - is_calibrated_ = true; - return true; -} - -bool Encoder::scan_for_enc_idx(float omega, float voltage_magnitude) { - index_found_ = false; - float phase = 0.0f; - axis_->run_control_loop([&](){ - phase = wrap_pm_pi(phase + omega * current_meas_period); - - float v_alpha = voltage_magnitude * arm_cos_f32(phase); - float v_beta = voltage_magnitude * arm_sin_f32(phase); - axis_->motor_.enqueue_voltage_timings(v_alpha, v_beta); - axis_->motor_.log_timing(Motor::TIMING_LOG_IDX_SEARCH); - - // continue until the index is found - return !index_found_; - }); - return axis_->error_ == Axis::ERROR_NO_ERROR; -} - -bool Encoder::run_calibration() { - float enc_calibration_voltage; - if (axis_->motor_.config_.motor_type == MOTOR_TYPE_HIGH_CURRENT) - enc_calibration_voltage = axis_->motor_.config_.calibration_current * axis_->motor_.config_.phase_resistance; - else if (axis_->motor_.config_.motor_type == MOTOR_TYPE_GIMBAL) - enc_calibration_voltage = axis_->motor_.config_.calibration_current; - else - return false; - - if (config_.use_index && !index_found_) - if (!scan_for_enc_idx( - (float)(axis_->motor_.config_.direction) * config_.idx_search_speed, - enc_calibration_voltage)) - return false; - if (!config_.hand_calibrated) // TODO: discuss what logic we want here - if (!calib_enc_offset(enc_calibration_voltage)) - return false; + offset_ = encvaluesum / (num_steps * 2); + is_ready_ = true; + config_.use_index = old_use_index; return true; } @@ -184,7 +198,7 @@ bool Encoder::update(float* pos_estimate, float* vel_estimate, float* phase_outp // compute electrical phase int corrected_enc = state_ % config_.cpr; - corrected_enc -= config_.offset; + corrected_enc -= offset_; //corrected_enc *= axis_->motor_.config_.direction; TODO: verify if this still works //TODO avoid recomputing elec_rad_per_enc every time float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / (float)(config_.cpr)); diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index cc1701a0..f3dbc78c 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -7,10 +7,15 @@ struct EncoderConfig_t { bool use_index = false; - bool hand_calibrated = false; + bool pre_calibrated = false; // If true, this means the offset stored in + // configuration is valid and does not need + // be determined by run_offset_calibration. + // In this case the encoder will enter ready + // state as soon as the index is found. float idx_search_speed = 10.0f; // [rad/s electrical] int32_t cpr = (2048 * 4); // Default resolution of CUI-AMT102 encoder, - int32_t offset = 0; + int32_t offset = 0; // If pre_calibrated is true, this is copied into encoder.offset_ once + // index search succeeds float calib_range = 0.02; }; @@ -34,8 +39,9 @@ public: bool calib_enc_offset(float voltage_magnitude); bool scan_for_enc_idx(float omega, float voltage_magnitude); + bool run_index_search(); + bool run_offset_calibration(); bool update(float* pos_estimate, float* vel_estimate, float* phase); - bool run_calibration(); const EncoderHardwareConfig_t& hw_config_; EncoderConfig_t& config_; @@ -43,8 +49,9 @@ public: Error_t error_ = ERROR_NONE; bool index_found_ = false; - bool is_calibrated_ = config_.hand_calibrated; + bool is_ready_ = false; int32_t state_ = 0; + int32_t offset_ = 0; float phase_ = 0.0f; // [rad] float pll_pos_ = 0.0f; // [rad] float pll_vel_ = 0.0f; // [rad/s] @@ -55,9 +62,10 @@ public: auto make_protocol_definitions() { return make_protocol_member_list( make_protocol_property("error", &error_), - make_protocol_ro_property("is_calibrated", &is_calibrated_), + make_protocol_ro_property("is_ready", &is_ready_), make_protocol_ro_property("index_found", const_cast(&index_found_)), make_protocol_property("state", &state_), + make_protocol_property("offset", &offset_), make_protocol_property("phase", &phase_), make_protocol_property("pll_pos", &pll_pos_), make_protocol_property("pll_vel", &pll_vel_), @@ -65,7 +73,7 @@ public: make_protocol_property("pll_ki", &pll_ki_), make_protocol_object("config", make_protocol_property("use_index", &config_.use_index), - make_protocol_property("hand_calibrated", &config_.hand_calibrated), + make_protocol_property("pre_calibrated", &config_.pre_calibrated), make_protocol_property("idx_search_speed", &config_.idx_search_speed), make_protocol_property("cpr", &config_.cpr), make_protocol_property("offset", &config_.offset), diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index caa133e7..2e771b27 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -36,7 +36,7 @@ typedef struct { // example: vel_gain is [V/(count/s)] instead of [A/(count/s)] // example: current_lim and calibration_current will instead determine the maximum voltage applied to the motor. typedef struct { - bool hand_calibrated = false; // can be set to true to indicate that all values here are valid + bool pre_calibrated = false; // can be set to true to indicate that all values here are valid int32_t pole_pairs = 7; // This value is correct for N5065 motors and Turnigy SK3 series. float calibration_current = 10.0f; // [A] float resistance_calib_max_voltage = 1.0f; // [V] - You may need to increase this if this voltage isn't sufficient to drive calibration_current through the motor. @@ -118,7 +118,7 @@ public: // variables exposed on protocol Error_t error_ = ERROR_NO_ERROR; - bool is_calibrated_ = config_.hand_calibrated; + bool is_calibrated_ = config_.pre_calibrated; Iph_BC_t current_meas_ = {0.0f, 0.0f}; Iph_BC_t DC_calib_ = {0.0f, 0.0f}; const float shunt_conductance_ = 1.0f / SHUNT_RESISTANCE; //[S] @@ -180,6 +180,7 @@ public: make_protocol_ro_property("TIMING_LOG_FOC_CURRENT", &timing_log_[TIMING_LOG_FOC_CURRENT]) ), make_protocol_object("config", + make_protocol_property("pre_calibrated", &config_.pre_calibrated), make_protocol_property("pole_pairs", &config_.pole_pairs), make_protocol_property("calibration_current", &config_.calibration_current), make_protocol_property("resistance_calib_max_voltage", &config_.resistance_calib_max_voltage), From 3e1d0aaad0e6e4a04154c1a885b372732187a767 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 23 Mar 2018 16:53:44 -0700 Subject: [PATCH 013/215] expose sensorless estimator on protocol --- Firmware/MotorControl/axis.hpp | 3 ++- Firmware/MotorControl/sensorless_estimator.hpp | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 65ef9d97..300c7e94 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -184,7 +184,8 @@ public: ), make_protocol_object("motor", motor_.make_protocol_definitions()), make_protocol_object("controller", controller_.make_protocol_definitions()), - make_protocol_object("encoder", encoder_.make_protocol_definitions()) + make_protocol_object("encoder", encoder_.make_protocol_definitions()), + make_protocol_object("sensorless_estimator", sensorless_estimator_.make_protocol_definitions()) ); } }; diff --git a/Firmware/MotorControl/sensorless_estimator.hpp b/Firmware/MotorControl/sensorless_estimator.hpp index 569c9a09..740ae8c1 100644 --- a/Firmware/MotorControl/sensorless_estimator.hpp +++ b/Firmware/MotorControl/sensorless_estimator.hpp @@ -26,6 +26,18 @@ public: float V_alpha_beta_memory_[2] = {0.0f, 0.0f}; // [V] float pm_flux_linkage_ = 1.58e-3f; // [V / (rad/s)] { 5.51328895422 / ( * ) } bool estimator_good_ = false; + + // Communication protocol definitions + auto make_protocol_definitions() { + return make_protocol_member_list( + make_protocol_property("error", &error_), + make_protocol_property("phase", &phase_), + make_protocol_property("pll_pos", &pll_pos_), + make_protocol_property("pll_vel", &pll_vel_), + make_protocol_property("pll_kp", &pll_kp_), + make_protocol_property("pll_ki", &pll_ki_) + ); + } }; #endif /* __SENSORLESS_ESTIMATOR_HPP */ From d71aa4ca8e2ff6953062ced1d728e7437afbf22c Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 23 Mar 2018 17:09:04 -0700 Subject: [PATCH 014/215] robustify python USB discovery/communication --- tools/odrive/usbbulk_transport.py | 8 +++++++- tools/odrive/utils.py | 19 +++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/tools/odrive/usbbulk_transport.py b/tools/odrive/usbbulk_transport.py index f513789f..5eecbd24 100644 --- a/tools/odrive/usbbulk_transport.py +++ b/tools/odrive/usbbulk_transport.py @@ -35,6 +35,12 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) return string def init(self): + # Under some conditions, the Linux USB/libusb stack ends up in a corrupt + # state where there are a few packets in a receive queue but a call + # to epr.read() does not return these packet until a new packet arrives. + # This undesirable queue can be cleared by resetting the device. + self.dev.reset() + try: if self.dev.is_kernel_driver_active(1): self.dev.detach_kernel_driver(1) @@ -106,7 +112,7 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) else: # Try resetting halt/stall condition try: - self.epw.clear_halt() + self.epr.clear_halt() except usb.core.USBError: raise odrive.protocol.ChannelBrokenException() # Retry transfer diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index be19adcd..a8c0e680 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -73,11 +73,15 @@ class Event(): """ Alternative to threading.Event(), enhanced by the subscribe() function that the original fails to provide. + @param Trigger: if supplied, the newly created event will be triggered + as soon as the trigger event becomes set """ - def __init__(self): + def __init__(self, trigger=None): self._evt = threading.Event() self._subscribers = [] self._mutex = threading.Lock() + if not trigger is None: + trigger.subscribe(self.set()) def is_set(self): return self._evt.is_set() @@ -120,7 +124,18 @@ class Event(): self._mutex.release() def wait(self, timeout=None): - return self._evt.wait(timeout=timeout) + if not self._evt.wait(timeout=timeout): + raise TimeoutError() + + def trigger_after(self, timeout): + """ + Triggers the event after the specified timeout. + This function returns immediately. + """ + def delayed_trigger(): + if not self.wait(timeout=timeout): + self.set() + threading.Thread(target=delayed_trigger, daemon=True).start() def wait_any(*events, timeout=None): """ From 153b904732893b52692d19f4e58b16d7e6b2b99c Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 23 Mar 2018 17:09:04 -0700 Subject: [PATCH 015/215] robustify python USB discovery/communication --- tools/odrive/usbbulk_transport.py | 8 +++++++- tools/odrive/utils.py | 19 +++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/tools/odrive/usbbulk_transport.py b/tools/odrive/usbbulk_transport.py index f513789f..5eecbd24 100644 --- a/tools/odrive/usbbulk_transport.py +++ b/tools/odrive/usbbulk_transport.py @@ -35,6 +35,12 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) return string def init(self): + # Under some conditions, the Linux USB/libusb stack ends up in a corrupt + # state where there are a few packets in a receive queue but a call + # to epr.read() does not return these packet until a new packet arrives. + # This undesirable queue can be cleared by resetting the device. + self.dev.reset() + try: if self.dev.is_kernel_driver_active(1): self.dev.detach_kernel_driver(1) @@ -106,7 +112,7 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) else: # Try resetting halt/stall condition try: - self.epw.clear_halt() + self.epr.clear_halt() except usb.core.USBError: raise odrive.protocol.ChannelBrokenException() # Retry transfer diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index be19adcd..a8c0e680 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -73,11 +73,15 @@ class Event(): """ Alternative to threading.Event(), enhanced by the subscribe() function that the original fails to provide. + @param Trigger: if supplied, the newly created event will be triggered + as soon as the trigger event becomes set """ - def __init__(self): + def __init__(self, trigger=None): self._evt = threading.Event() self._subscribers = [] self._mutex = threading.Lock() + if not trigger is None: + trigger.subscribe(self.set()) def is_set(self): return self._evt.is_set() @@ -120,7 +124,18 @@ class Event(): self._mutex.release() def wait(self, timeout=None): - return self._evt.wait(timeout=timeout) + if not self._evt.wait(timeout=timeout): + raise TimeoutError() + + def trigger_after(self, timeout): + """ + Triggers the event after the specified timeout. + This function returns immediately. + """ + def delayed_trigger(): + if not self.wait(timeout=timeout): + self.set() + threading.Thread(target=delayed_trigger, daemon=True).start() def wait_any(*events, timeout=None): """ From b5f47f045e926f4d3e828aa5dd4b4ed9b64c4b96 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 23 Mar 2018 19:48:58 -0700 Subject: [PATCH 016/215] add automated test script --- Firmware/Makefile | 6 +- Firmware/test-rig.yaml | 25 +++++ tools/odrive/enums.py | 24 +++++ tools/odrive/tests.py | 235 +++++++++++++++++++++++++++++++++++++++++ tools/odrive/utils.py | 68 ++++++++++++ tools/run_tests.py | 120 +++++++++++++++++++++ 6 files changed, 477 insertions(+), 1 deletion(-) create mode 100644 Firmware/test-rig.yaml create mode 100644 tools/odrive/enums.py create mode 100644 tools/odrive/tests.py create mode 100755 tools/run_tests.py diff --git a/Firmware/Makefile b/Firmware/Makefile index 84a28e30..1fdd4897 100644 --- a/Firmware/Makefile +++ b/Firmware/Makefile @@ -24,9 +24,13 @@ bmp: all --ex 'attach 1' \ --ex 'load' $(FIRMWARE) +# Erase entire STM32 +erase: + openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg -c init -c reset\ halt -c flash\ erase_address\ 0x8000000\ 0x100000 -c reset\ run -c exit + # Erase all configuration from the ODrive erase_config: - openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg -c init -c reset\ halt -c flash\ erase_address\ 0x80C0000\ 0x40000 -c reset\ run -c exit + openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg -c init -c reset\ halt -c flash\ erase_address\ 0x80C0000\ 0x40000 -c reset\ init -c reset\ run -c exit clean: -rm -fR .dep $(BUILD_DIR) diff --git a/Firmware/test-rig.yaml b/Firmware/test-rig.yaml new file mode 100644 index 00000000..875898b3 --- /dev/null +++ b/Firmware/test-rig.yaml @@ -0,0 +1,25 @@ + +# ODrives +odrives: + - board-version: v3.4-24V + serial-number: "385F324D3037" + brake-resistance: 0.47 + uart: /dev/serial/by-id/... + usb: auto + programmer: /dev... + axes: + - motor-phase-resistance: 0.033 + motor-phase-inductance: 1.6e-05 + motor-pole-pairs: 7 + motor-direction: 1 + encoder-cpr: 8192 + - motor-phase-resistance: 0.028 + motor-phase-inductance: 1.6e-05 + motor-pole-pairs: 7 + motor-direction: -1 + encoder-cpr: 8192 + +# Mechanical couplings +couplings: + #- [ odrive0.axis0, odrive1.axis0 ] + #- [ odrive0.axis1, odrive1.axis1 ] diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py new file mode 100644 index 00000000..14870c2d --- /dev/null +++ b/tools/odrive/enums.py @@ -0,0 +1,24 @@ + +# TODO: transmit enums over protocol + +AXIS_STATE_UNDEFINED = 0 +AXIS_STATE_IDLE = 1 +AXIS_STATE_STARTUP_SEQUENCE = 2 +AXIS_STATE_FULL_CALIBRATION_SEQUENCE = 3 +AXIS_STATE_MOTOR_CALIBRATION = 4 +AXIS_STATE_SENSORLESS_CONTROL = 5 +AXIS_STATE_ENCODER_INDEX_SEARCH = 6 +AXIS_STATE_ENCODER_OFFSET_CALIBRATION = 7 +AXIS_STATE_CLOSED_LOOP_CONTROL = 8 + +AXIS_ERROR_NO_ERROR = 0 +AXIS_ERROR_INVALID_STATE = 1 +AXIS_ERROR_DC_BUS_UNDER_VOLTAGE = 2 +AXIS_ERROR_DC_BUS_OVER_VOLTAGE = 3 +AXIS_ERROR_CURRENT_MEASUREMENT_TIMEOUT = 4 +AXIS_ERROR_CONTROL_LOOP_TIMEOUT = 5 +AXIS_ERROR_MOTOR_FAILED = 6 +AXIS_ERROR_SENSORLESS_ESTIMATOR_FAILED = 7 +AXIS_ERROR_ENCODER_FAILED = 8 +AXIS_ERROR_CONTROLLER_FAILED = 9 +AXIS_ERROR_POS_CTRL_DURING_SENSORLESS = 10 diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py new file mode 100644 index 00000000..924dbee8 --- /dev/null +++ b/tools/odrive/tests.py @@ -0,0 +1,235 @@ + +import subprocess +import shlex +import math +import time +import sys +import odrive.discovery +from odrive.enums import * + +import abc +ABC = abc.ABC + +class TestFailed(Exception): + def __init__(self, message): + Exception.__init__(self, message) + +def test_assert_eq(observed, expected, range=None, accuracy=None): + if range is None and accuracy is None and observed != expected: + raise TestFailed("value mismatch: expected {} but observed {}".format(expected, observed)) + if not range is None and ((observed < expected - range) or (observed > expected + range)): + raise TestFailed("value out of range: expected {}+-{} but observed {}".format(expected, range, observed)) + elif not accuracy is None and ((observed < expected * (1 - accuracy)) or (observed > expected * (1 + accuracy))): + raise TestFailed("value out of range: expected {}+-{}% but observed {}".format(expected, accuracy*100.0, observed)) + +def run(command_line, logger, timeout=None): + """ + Runs a shell command in the Firmware directory + """ + logger.debug("invoke: " + command_line) + cmd = shlex.split(command_line) + result = subprocess.run(cmd, cwd='../Firmware', timeout=timeout, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + if result.returncode != 0: + logger.error(result.stdout.decode(sys.stdout.encoding)) + raise TestFailed("command {} failed".format(command_line)) + +def rediscover(odrv_yaml): + """ + Connects to the ODrive indicated by odrv_yaml + """ + odrv = odrive.discovery.find_any(path="usb", serial_number=odrv_yaml['serial-number'], timeout=10) + odrv_yaml['odrv'] = odrv + for axis_idx, axis_yaml in enumerate(odrv_yaml['axes']): + axis_yaml['axis'] = odrv.__dict__['axis{}'.format(axis_idx)] + return odrv + +class ODriveTest(ABC): + """ + Tests inheriting from this class get full ownership of the ODrive + being tested. However no guarantees are made for the mechanical + state of the axes. + """ + @abc.abstractmethod + def run_test(self, odrv, odrv_config, logger): + pass + +class AxisTest(ABC): + """ + Tests inheriting from this class get ownership of one axis of + an ODrive. If the axis is mechanically coupled to another + axis, the other axis is guaranteed to be disabled (high impedance) + during this test. + """ + @abc.abstractmethod + def run_test(self, axis, axis_config, logger): + pass + +class DualAxisTest(ABC): + """ + Tests using this scope get ownership of two axes that are mechanically + coupled. + """ + @abc.abstractmethod + def run_test(self, axis0, axis0_config, axis1, axis1_config, logger): + pass + +class TestFlashAndErase(ODriveTest): + def run_test(self, odrv, odrv_config, logger): + run("make flash PROGRAMMER='" + odrv_config['programmer'] + "'", logger, timeout=20) + # FIXME: device does not reboot correctly after erasing config this way + #run("make erase_config PROGRAMMER='" + test_rig.programmer + "'", timeout=10) + + logger.debug("waiting for ODrive...") + odrv = rediscover(odrv_config) + # ensure the correct odrive is returned + test_assert_eq(format(odrv.serial_number, 'x').upper(), odrv_config['serial-number']) + + # erase configuration and reboot + logger.debug("erasing old configuration...") + odrv.erase_configuration() + #time.sleep(0.1) + try: + # FIXME: sometimes the device does not reappear after this ("no response - probably incompatible") + # this is a firmware issue since it persists when unplugging/replugging + # but goes away when power cycling the device + odrv.reboot() + except odrive.protocol.ChannelBrokenException: + pass # this is expected + time.sleep(0.5) + +class TestSetup(ODriveTest): + """ + Preconditions: ODrive is unconfigured and just rebooted + """ + def run_test(self, odrv, odrv_config, logger): + odrv = rediscover(odrv_config) + + # initial protocol tests and setup + logger.debug("setting up ODrive...") + odrv.config.enable_uart = True + test_assert_eq(odrv.config.enable_uart, True) + odrv.config.enable_uart = False + test_assert_eq(odrv.config.enable_uart, False) + odrv.config.brake_resistance = 1.0 + test_assert_eq(odrv.config.brake_resistance, 1.0) + odrv.config.brake_resistance = odrv_config['brake-resistance'] + test_assert_eq(odrv.config.brake_resistance, odrv_config['brake-resistance'], accuracy=0.01) + + # firmware has 1500ms startup delay + time.sleep(2) + + logger.debug("ensure we're in idle state") + test_assert_eq(odrv.axis0.current_state, AXIS_STATE_IDLE) + test_assert_eq(odrv.axis1.current_state, AXIS_STATE_IDLE) + +def request_state(axis, state, expect_success=True): + axis.requested_state = state + time.sleep(0.001) + if expect_success: + test_assert_eq(axis.current_state, state) + else: + test_assert_eq(axis.current_state, AXIS_STATE_IDLE) + test_assert_eq(axis.error, AXIS_ERROR_INVALID_STATE) + axis.error = AXIS_ERROR_NO_ERROR # reset error + +class TestMotorCalibration(AxisTest): + """ + Tests motor calibration. + The calibration results are compared against well known test rig values. + Preconditions: The motor must be uncalibrated. + Postconditions: The motor will be calibrated after this test. + """ + def run_test(self, axis, axis_config, logger): + logger.debug("try to enter closed loop control (should be rejected)") + request_state(axis, AXIS_STATE_CLOSED_LOOP_CONTROL, expect_success=False) + + logger.debug("try to start encoder index search (should be rejected)") + request_state(axis, AXIS_STATE_ENCODER_INDEX_SEARCH, expect_success=False) + + logger.debug("try to start encoder offset calibration (should be rejected)") + request_state(axis, AXIS_STATE_ENCODER_OFFSET_CALIBRATION, expect_success=False) + + logger.debug("motor calibration (takes about 4.5 seconds)") + axis.motor.config.pole_pairs = axis_config['motor-pole-pairs'] + request_state(axis, AXIS_STATE_MOTOR_CALIBRATION) + time.sleep(6) + test_assert_eq(axis.current_state, AXIS_STATE_IDLE) + test_assert_eq(axis.error, AXIS_ERROR_NO_ERROR) + test_assert_eq(axis.motor.config.phase_resistance, axis_config['motor-phase-resistance'], accuracy=0.1) + test_assert_eq(axis.motor.config.phase_inductance, axis_config['motor-phase-inductance'], accuracy=0.5) + axis.motor.config.pre_calibrated = True + +class TestEncoderOffsetCalibration(AxisTest): + """ + Tests encoder offset calibration. + Preconditions: The encoder must be non-ready. + Postconditions: The encoder will be ready after this test. + """ + def run_test(self, axis, axis_config, logger): + logger.debug("try to enter closed loop control (should be rejected)") + request_state(axis, AXIS_STATE_CLOSED_LOOP_CONTROL, expect_success=False) + + logger.debug("encoder offset calibration (takes about 9.5 seconds)") + axis.encoder.config.cpr = axis_config['encoder-cpr'] # TODO: test setting a wrong CPR + request_state(axis, AXIS_STATE_ENCODER_OFFSET_CALIBRATION) + # TODO: ensure the encoder calibration doesn't do crap + time.sleep(11) + test_assert_eq(axis.current_state, AXIS_STATE_IDLE) + test_assert_eq(axis.error, AXIS_ERROR_NO_ERROR) + test_assert_eq(axis.motor.config.direction, axis_config['motor-direction']) + axis.encoder.config.pre_calibrated = True + +class TestClosedLoopControl(AxisTest): + """ + Tests closed loop position control and velocity control + and verifies that the sensorless estimator works + Precondition: The axis is calibrated and ready for closed loop control + """ + def run_test(self, axis, axis_config, logger): + logger.debug("closed loop control: test tiny position changes") + axis.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL + time.sleep(0.001) + test_assert_eq(axis.current_state, AXIS_STATE_CLOSED_LOOP_CONTROL) + time.sleep(0.1) # give the PLL some time to settle + test_assert_eq(axis.encoder.pll_pos, 0, range=300) + axis.controller.set_pos_setpoint(1000, 0, 0) + time.sleep(0.5) + test_assert_eq(axis.encoder.pll_pos, 1000, range=200) + axis.controller.set_pos_setpoint(-1000, 0, 0) + time.sleep(0.5) + test_assert_eq(axis.encoder.pll_pos, -1000, range=200) + + logger.debug("closed loop control: test vel_limit") + axis.controller.set_pos_setpoint(50000, 0, 0) + axis.controller.config.vel_limit = 40000 + time.sleep(0.3) + test_assert_eq(axis.encoder.pll_vel, 40000, range=4000) + expected_sensorless_estimation = 40000 * 2 * math.pi / axis_config['encoder-cpr'] * axis_config['motor-pole-pairs'] + test_assert_eq(axis.sensorless_estimator.pll_vel, expected_sensorless_estimation, range=50) + time.sleep(3) + test_assert_eq(axis.encoder.pll_vel, 0, range=1000) + +class TestStoreAndReboot(ODriveTest): + """ + Stores the current configuration to NVM and reboots. + """ + def run_test(self, odrv, odrv_config, logger): + logger.debug("storing configuration and rebooting...") + odrv.save_configuration() + try: + odrv.reboot() + except odrive.protocol.ChannelBrokenException: + pass # this is expected + time.sleep(2) + + odrv = rediscover(odrv_config) + + logger.debug("verifying configuration after reboot...") + test_assert_eq(odrv.config.brake_resistance, odrv_config['brake-resistance'], accuracy=0.01) + for axis_config in odrv_config['axes']: + axis = axis_config['axis'] + test_assert_eq(axis.encoder.config.cpr, axis_config['encoder-cpr']) + test_assert_eq(axis.motor.config.phase_resistance, axis_config['motor-phase-resistance'], accuracy=0.1) + test_assert_eq(axis.motor.config.phase_inductance, axis_config['motor-phase-inductance'], accuracy=0.5) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index a8c0e680..4f070b1e 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -154,3 +154,71 @@ def wait_any(*events, timeout=None): if events[i].is_set(): return i raise TimeoutException() + + +def for_all_parallel(objects, get_name, callback): + """ + Executes the specified callback for every object in the objects + list concurrently. This function waits for all callbacks to + finish and throws an exception if any of the callbacks throw + an exception. + """ + tracebacks = [] + + def run_callback(element): + try: + callback(element) + except Exception as ex: + tracebacks.append((get_name(element), ex)) + + # Start a thread for each element in the list + all_threads = [] + for element in objects: + thread = threading.Thread(target=run_callback, args=(element,)) + thread.start() + all_threads.append(thread) + + # Wait for all threads to complete + for thread in all_threads: + thread.join() + + if len(tracebacks) == 1: + msg = "task {} failed.".format(tracebacks[0][0]) + raise Exception(msg) from tracebacks[0][1] + elif len(tracebacks) > 1: + msg = "task {} and {} failed.".format( + tracebacks[0][0], + "one other" if len(tracebacks) == 2 else str(len(tracebacks)-1) + " others" + ) + raise Exception(msg) from tracebacks[0][1] + + +class Logger(): + """ + Logs messages to stdout + """ + + COLOR_GREEN = '\x1b[92;1m' + COLOR_CYAN = '\x1b[96;1m' + COLOR_YELLOW = '\x1b[93;1m' + COLOR_RED = '\x1b[91;1m' + COLOR_RESET = '\x1b[0m' + + def __init__(self): + self._prefix = '' + + def indent(self, prefix=' '): + indented_logger = Logger() + indented_logger._prefix = self._prefix + prefix + return indented_logger + + def debug(self, text): + print(self._prefix + text) + def success(self, text): + print(self._prefix + Logger.COLOR_GREEN + text + Logger.COLOR_RESET) + def info(self, text): + print(self._prefix + Logger.COLOR_CYAN + text + Logger.COLOR_RESET) + def warn(self, text): + print(self._prefix + Logger.COLOR_YELLOW + text + Logger.COLOR_RESET) + def error(self, text): + print(self._prefix + Logger.COLOR_RED + text + Logger.COLOR_RESET) diff --git a/tools/run_tests.py b/tools/run_tests.py new file mode 100755 index 00000000..c070b919 --- /dev/null +++ b/tools/run_tests.py @@ -0,0 +1,120 @@ +#!/bin/env python3 +# +# This script tests various functions of the ODrive firmware and +# the ODrive Python library. +# +# Usage: +# 1. adapt test-rig.yaml for your test rig. +# 2. ./run_tests.py + +import yaml +import os +import sys +import threading +import traceback +from odrive.tests import * +from odrive.utils import Logger, for_all_parallel + + +all_tests = [ + TestFlashAndErase(), + TestSetup(), + TestMotorCalibration(), + # TODO: test encoder index search + TestEncoderOffsetCalibration(), + TestClosedLoopControl(), + TestStoreAndReboot(), + TestEncoderOffsetCalibration(), # need to find offset _or_ index after reboot + TestClosedLoopControl() + # TODO: test step/dir + # TODO: test sensorless + # TODO: test ASCII protocol + # TODO: test protocol over UART +] + + +logger = Logger() + +with open('test-rig.yaml', 'r') as file_stream: + test_rig_yaml = yaml.load(file_stream) + +# Ensure every device has a name +for idx, odrv_yaml in enumerate(test_rig_yaml['odrives']): + if not 'name' in odrv_yaml: + odrv_yaml['name'] = 'odrive{}'.format(idx) + +# Build a dictionary of axes by name (e.g. odrive0.axis0) +# Also ensure every axis has a name and mutex +axes_by_name = {} +for odrv_yaml in test_rig_yaml['odrives']: + for axis_idx, axis_yaml in enumerate(odrv_yaml['axes']): + if not 'name' in axis_yaml: + axis_yaml['name'] = '{}.axis{}'.format(odrv_yaml['name'], axis_idx) + axis_yaml['lock'] = threading.Lock() + axes_by_name[axis_yaml['name']] = axis_yaml + +# Ensure mechanical couplings are valid +if test_rig_yaml['couplings'] is None: + test_rig_yaml['couplings'] = {} +else: + for axis in sum(test_rig_yaml['couplings'], []): + if not axis in axes_by_name: + logger.error('Unknown axis {} in list of mechanical couplings'.format(axis)) + + +try: + for test in all_tests: + if isinstance(test, ODriveTest): + def odrv_test_thread(odrv_yaml): + test_subject_name = odrv_yaml['name'] + logger.info('● running {} on {}...'.format(type(test).__name__, test_subject_name)) + odrv = odrv_yaml['odrv'] if 'odrv' in odrv_yaml else None + test.run_test(odrv, odrv_yaml, + logger.indent(' {}: '.format(test_subject_name))) + + for_all_parallel(test_rig_yaml['odrives'], lambda x: x['name'], odrv_test_thread) + + elif isinstance(test, AxisTest): + def axis_test_thread(axis_name): + # Get all axes that are mechanically coupled with the axis specified by axis_name + conflicting_axes = sum([c for c in test_rig_yaml['couplings'] if (axis_name in c)], []) + # Remove duplicates + conflicting_axes = list(set(conflicting_axes)) + # Acquire lock for all conflicting axes + conflicting_axes.sort() # prevent deadlocks + for conflicting_axis in conflicting_axes: + axes_by_name[conflicting_axis]['lock'].acquire() + try: + # Run test on this axis + logger.info('● running {} on {}...'.format(type(test).__name__, axis_name)) + axis_yaml = axes_by_name[axis_name] + test.run_test(axis_yaml['axis'], axis_yaml, + logger.indent(' {}: '.format(axis_name))) + finally: + # Release all conflicting axes + for conflicting_axis in conflicting_axes: + axes_by_name[conflicting_axis]['lock'].release() + + for_all_parallel(axes_by_name, lambda x: x, axis_test_thread) + + else: + logger.warn("ignoring unknown test type {}".format(type(test))) + +except: + logger.error(traceback.format_exc()) + logger.debug('=> Test failed. Please wait while I secure the test rig...') + try: + dont_secure_after_failure = True # TODO: disable + if not dont_secure_after_failure: + def odrv_reset_thread(odrv_yaml): + run("make erase PROGRAMMER='" + odrv_yaml['programmer'] + "'", logger, timeout=30) + for_all_parallel(test_rig_yaml['odrives'], lambda x: x['name'], odrv_reset_thread) + except: + logger.error('///////////////////////////////////////////') + logger.error('/// CRITICAL: COULD NOT SECURE TEST RIG ///') + logger.error('/// CUT THE POWER IMMEDIATELY! ///') + logger.error('///////////////////////////////////////////') + else: + logger.error('some test failed!') +else: + logger.success('All tests succeeded!') From c547bb09a244e7d5d448f858cd460555835def05 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 23 Mar 2018 19:50:35 -0700 Subject: [PATCH 017/215] more USB and discovery fixes --- tools/odrive/discovery.py | 24 ++++++++++++------------ tools/odrive/usbbulk_transport.py | 6 ++++++ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/tools/odrive/discovery.py b/tools/odrive/discovery.py index f31f0a2d..18b5107a 100644 --- a/tools/odrive/discovery.py +++ b/tools/odrive/discovery.py @@ -12,6 +12,7 @@ import odrive.utils import odrive.remote_object import odrive.usbbulk_transport import odrive.serial_transport +from odrive.utils import Event channel_types = { "usb": odrive.usbbulk_transport.discover_channels, @@ -59,7 +60,7 @@ def find_all(path, serial_number, return json_data = {"name": "odrive", "members": json_data} obj = odrive.remote_object.RemoteObject(json_data, None, channel, printer) - device_serial_number = serial_number if hasattr(obj, 'serial_number') else "[unknown serial number]" + device_serial_number = format(obj.serial_number, 'x').upper() if hasattr(obj, 'serial_number') else "[unknown serial number]" if serial_number != None and device_serial_number != serial_number: printer("Ignoring device with serial number {}".format(device_serial_number)) return @@ -78,19 +79,18 @@ def find_all(path, serial_number, raise Exception("Invalid path spec \"{}\"".format(search_spec)) -def find_any(path="usb", serial_number=None, printer=noprint): +def find_any(path="usb", serial_number=None, cancellation_token=None, timeout=None, printer=noprint): """ Blocks until the first matching ODrive is connected and then returns that device """ - cancellation_token = None # TODO: make this a parameter (see todo below) - if cancellation_token is None: - cancellation_token = threading.Event() - done_signal = threading.Event() + result = [ None ] + done_signal = Event(cancellation_token) def did_discover_object(obj): - global result - result = obj + result[0] = obj done_signal.set() - find_all(path, serial_number, did_discover_object, cancellation_token, printer) - done_signal.wait() # TODO: wait on done_signal OR cancellation_token - cancellation_token.set() - return result + find_all(path, serial_number, did_discover_object, done_signal, printer) + try: + done_signal.wait(timeout=timeout) + finally: + done_signal.set() # terminate find_all + return result[0] diff --git a/tools/odrive/usbbulk_transport.py b/tools/odrive/usbbulk_transport.py index 5eecbd24..edf057e2 100644 --- a/tools/odrive/usbbulk_transport.py +++ b/tools/odrive/usbbulk_transport.py @@ -87,7 +87,10 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) except usb.core.USBError as ex: if ex.errno == 19: # "no such device" raise odrive.protocol.ChannelBrokenException() + elif ex.errno == 110: # timeout + raise odrive.utils.TimeoutException() else: + self._printer("halt condition: {}".format(ex.errno)) # Try resetting halt/stall condition try: self.epw.clear_halt() @@ -109,7 +112,10 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) except usb.core.USBError as ex: if ex.errno == 19: # "no such device" raise odrive.protocol.ChannelBrokenException() + elif ex.errno == 110: # timeout + raise odrive.utils.TimeoutException() else: + self._printer("halt condition: {}".format(ex.errno)) # Try resetting halt/stall condition try: self.epr.clear_halt() From fe7a87afecf14dfde664d4f614a7e047bee5466b Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 23 Mar 2018 19:51:36 -0700 Subject: [PATCH 018/215] fix sensorless estimator to respect motor direction --- Firmware/MotorControl/sensorless_estimator.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/sensorless_estimator.cpp b/Firmware/MotorControl/sensorless_estimator.cpp index c1358150..0fa3c7fe 100644 --- a/Firmware/MotorControl/sensorless_estimator.cpp +++ b/Firmware/MotorControl/sensorless_estimator.cpp @@ -33,6 +33,9 @@ bool SensorlessEstimator::update(float* pos_estimate, float* vel_estimate, float -axis_->motor_.current_meas_.phB - axis_->motor_.current_meas_.phC, one_by_sqrt3 * (axis_->motor_.current_meas_.phB - axis_->motor_.current_meas_.phC)}; + // Swap sign of I_beta if motor is reversed + I_alpha_beta[1] *= axis_->motor_.config_.direction; + // alpha-beta vector operations float eta[2]; for (int i = 0; i <= 1; ++i) { @@ -68,7 +71,7 @@ bool SensorlessEstimator::update(float* pos_estimate, float* vel_estimate, float // Flux state estimation done, store V_alpha_beta for next timestep V_alpha_beta_memory_[0] = axis_->motor_.current_control_.final_v_alpha; - V_alpha_beta_memory_[1] = axis_->motor_.current_control_.final_v_beta; + V_alpha_beta_memory_[1] = axis_->motor_.current_control_.final_v_beta * axis_->motor_.config_.direction; // PLL // TODO: the PLL part has some code duplication with the encoder PLL From 765e63af01a4ce199c69027ae49da2359c6ff7bf Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 23 Mar 2018 19:53:46 -0700 Subject: [PATCH 019/215] set current controller gains based on NVM configration If the motor phase resistance and phase inductance was loaded from NVM (pre_calibrated = true) (as opposed to calibration) the current control P and I gains would not be loaded correctly. This commit fixes this. --- Firmware/MotorControl/motor.cpp | 17 ++++++++++++----- Firmware/MotorControl/motor.hpp | 2 ++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 75e81202..5979257c 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -58,6 +58,17 @@ void Motor::disarm() { axis_->missed_control_deadline_ = true; } +// @brief Tune the current controller based on phase resistance and inductance +// This should be invoked whenever one of these values changes. +// TODO: allow update on user-request or update automatically via hooks +void Motor::update_current_controller_gains() { + // Calculate current control gains + float current_control_bandwidth = 1000.0f; // [rad/s] + current_control_.p_gain = current_control_bandwidth * config_.phase_inductance; + float plant_pole = config_.phase_resistance / config_.phase_inductance; + current_control_.i_gain = plant_pole * current_control_.p_gain; +} + // @brief Set up the gate drivers void Motor::DRV8301_setup() { DRV_SPI_8301_Vars_t* local_regs = &gate_driver_regs_; @@ -234,11 +245,7 @@ bool Motor::run_calibration() { return false; } - // Calculate current control gains - float current_control_bandwidth = 1000.0f; // [rad/s] - current_control_.p_gain = current_control_bandwidth * config_.phase_inductance; - float plant_pole = config_.phase_resistance / config_.phase_inductance; - current_control_.i_gain = plant_pole * current_control_.p_gain; + update_current_controller_gains(); is_calibrated_ = true; return true; diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 2e771b27..37647db0 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -82,8 +82,10 @@ public: bool arm(); void disarm(); void setup() { + update_current_controller_gains(); DRV8301_setup(); } + void update_current_controller_gains(); void DRV8301_setup(); bool check_DRV_fault(); bool do_checks(); From 3373ce29a8a0d9f2cd3476c2d7370212634cdc5c Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 23 Mar 2018 19:50:35 -0700 Subject: [PATCH 020/215] more USB and discovery fixes --- tools/odrive/discovery.py | 24 ++++++++++++------------ tools/odrive/usbbulk_transport.py | 6 ++++++ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/tools/odrive/discovery.py b/tools/odrive/discovery.py index f31f0a2d..18b5107a 100644 --- a/tools/odrive/discovery.py +++ b/tools/odrive/discovery.py @@ -12,6 +12,7 @@ import odrive.utils import odrive.remote_object import odrive.usbbulk_transport import odrive.serial_transport +from odrive.utils import Event channel_types = { "usb": odrive.usbbulk_transport.discover_channels, @@ -59,7 +60,7 @@ def find_all(path, serial_number, return json_data = {"name": "odrive", "members": json_data} obj = odrive.remote_object.RemoteObject(json_data, None, channel, printer) - device_serial_number = serial_number if hasattr(obj, 'serial_number') else "[unknown serial number]" + device_serial_number = format(obj.serial_number, 'x').upper() if hasattr(obj, 'serial_number') else "[unknown serial number]" if serial_number != None and device_serial_number != serial_number: printer("Ignoring device with serial number {}".format(device_serial_number)) return @@ -78,19 +79,18 @@ def find_all(path, serial_number, raise Exception("Invalid path spec \"{}\"".format(search_spec)) -def find_any(path="usb", serial_number=None, printer=noprint): +def find_any(path="usb", serial_number=None, cancellation_token=None, timeout=None, printer=noprint): """ Blocks until the first matching ODrive is connected and then returns that device """ - cancellation_token = None # TODO: make this a parameter (see todo below) - if cancellation_token is None: - cancellation_token = threading.Event() - done_signal = threading.Event() + result = [ None ] + done_signal = Event(cancellation_token) def did_discover_object(obj): - global result - result = obj + result[0] = obj done_signal.set() - find_all(path, serial_number, did_discover_object, cancellation_token, printer) - done_signal.wait() # TODO: wait on done_signal OR cancellation_token - cancellation_token.set() - return result + find_all(path, serial_number, did_discover_object, done_signal, printer) + try: + done_signal.wait(timeout=timeout) + finally: + done_signal.set() # terminate find_all + return result[0] diff --git a/tools/odrive/usbbulk_transport.py b/tools/odrive/usbbulk_transport.py index 5eecbd24..edf057e2 100644 --- a/tools/odrive/usbbulk_transport.py +++ b/tools/odrive/usbbulk_transport.py @@ -87,7 +87,10 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) except usb.core.USBError as ex: if ex.errno == 19: # "no such device" raise odrive.protocol.ChannelBrokenException() + elif ex.errno == 110: # timeout + raise odrive.utils.TimeoutException() else: + self._printer("halt condition: {}".format(ex.errno)) # Try resetting halt/stall condition try: self.epw.clear_halt() @@ -109,7 +112,10 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) except usb.core.USBError as ex: if ex.errno == 19: # "no such device" raise odrive.protocol.ChannelBrokenException() + elif ex.errno == 110: # timeout + raise odrive.utils.TimeoutException() else: + self._printer("halt condition: {}".format(ex.errno)) # Try resetting halt/stall condition try: self.epr.clear_halt() From 0784bd341376be5c410ecabe9b38829e0fabf897 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 23 Mar 2018 20:21:25 -0700 Subject: [PATCH 021/215] move test-rig.yaml to tools/ --- tools/odrive/tests.py | 4 ++-- tools/run_tests.py | 5 ++++- {Firmware => tools}/test-rig.yaml | 0 3 files changed, 6 insertions(+), 3 deletions(-) rename {Firmware => tools}/test-rig.yaml (100%) diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index 924dbee8..4e9b4398 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -24,11 +24,11 @@ def test_assert_eq(observed, expected, range=None, accuracy=None): def run(command_line, logger, timeout=None): """ - Runs a shell command in the Firmware directory + Runs a shell command in the current directory """ logger.debug("invoke: " + command_line) cmd = shlex.split(command_line) - result = subprocess.run(cmd, cwd='../Firmware', timeout=timeout, + result = subprocess.run(cmd, timeout=timeout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if result.returncode != 0: diff --git a/tools/run_tests.py b/tools/run_tests.py index c070b919..463d6939 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -35,9 +35,12 @@ all_tests = [ logger = Logger() -with open('test-rig.yaml', 'r') as file_stream: +script_path=os.path.dirname(os.path.realpath(__file__)) +with open(script_path + '/test-rig.yaml', 'r') as file_stream: test_rig_yaml = yaml.load(file_stream) +os.chdir(script_path + '/../Firmware') + # Ensure every device has a name for idx, odrv_yaml in enumerate(test_rig_yaml['odrives']): if not 'name' in odrv_yaml: diff --git a/Firmware/test-rig.yaml b/tools/test-rig.yaml similarity index 100% rename from Firmware/test-rig.yaml rename to tools/test-rig.yaml From ae8acae7ef7e0ea08297bf0ee1072f09ed125f8d Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sun, 25 Mar 2018 15:57:50 -0700 Subject: [PATCH 022/215] make fancy terminal features work on Windows Add Windows support for the following terminal features: - colored output - output on the second last line On Unix systems, VT100 escape codes are used to achieve this, functionality however Windows <10 doesn't interpret VT100 escape codes. For normal colored output, we use the colorama module to abstract this away. To print text on the second-last line we call the appropriate Win32 API functions directly (using the win32console module). --- tools/explore_odrive.py | 31 ++------- tools/odrive/serial_transport.py | 2 +- tools/odrive/usbbulk_transport.py | 8 ++- tools/odrive/utils.py | 102 +++++++++++++++++++++++++++--- 4 files changed, 105 insertions(+), 38 deletions(-) diff --git a/tools/explore_odrive.py b/tools/explore_odrive.py index ccc252d4..66884d0f 100755 --- a/tools/explore_odrive.py +++ b/tools/explore_odrive.py @@ -8,7 +8,7 @@ import sys import platform import threading import odrive.discovery -from odrive.utils import start_liveplotter +from odrive.utils import start_liveplotter, Logger # Flush stdout by default import functools @@ -63,27 +63,7 @@ else: ## Interactive console utils ## -COLOR_RED = '\x1b[91;1m' -COLOR_CYAN = '\x1b[96;1m' -COLOR_RESET = '\x1b[0m' - -def print_on_second_last_line(text, **kwargs): - """ - Prints a text on the second last line. - This can be used to print a message above the command - prompt. If the command prompt spans multiple lines, - there will be glitches. - """ - # Escape character sequence: - # ESC 7: store cursor position - # ESC 1A: move cursor up by one - # ESC 1S: scroll entire viewport by one - # ESC 1L: insert 1 line at cursor position - # (print text) - # ESC 8: restore old cursor position - kwargs['end'] = '' - kwargs['flush'] = True - print('\x1b7\x1b[1A\x1b[1S\x1b[1L' + text + '\x1b8', **kwargs) +logger = Logger() def print_banner(): print('ODrive control utility v0.4') @@ -135,7 +115,7 @@ def did_discover_device(odrive): # Publish new ODrive to interactive console interactive_variables[interactive_name] = odrive globals()[interactive_name] = odrive # Add to globals so tab complete works - print_on_second_last_line(COLOR_CYAN + "{} to ODrive {:012X} as {}".format(verb, serial_number, interactive_name) + COLOR_RESET) + logger.info("{} to ODrive {:012X} as {}".format(verb, serial_number, interactive_name)) # Subscribe to disappearance of the device odrive.__channel__._channel_broken.subscribe(lambda: did_lose_device(interactive_name)) @@ -146,7 +126,7 @@ def did_lose_device(interactive_name): a message. """ if not app_shutdown_token.is_set(): - print_on_second_last_line(COLOR_RED + "Oh no {} disappeared".format(interactive_name) + COLOR_RESET) + logger.warn("Oh no {} disappeared".format(interactive_name)) # Connect to device printer("Waiting for device...") @@ -183,7 +163,7 @@ else: # Enable tab complete if possible try: import rlcompleter - import readline + import readline # Works only on Unix readline.parse_and_bind("tab: complete") except: sudo_prefix = "" if platform.system() == "Windows" else "sudo " @@ -206,5 +186,6 @@ console.runcode('sys.excepthook=newexcepthook') # Launch shell print_banner() +logger._skip_bottom_line = True interact() app_shutdown_token.set() diff --git a/tools/odrive/serial_transport.py b/tools/odrive/serial_transport.py index 53ca6850..dd595c6d 100644 --- a/tools/odrive/serial_transport.py +++ b/tools/odrive/serial_transport.py @@ -89,7 +89,7 @@ def discover_channels(path, serial_number, callback, cancellation_token, printer input_stream, output_stream, printer) channel.serial_device = serial_device except serial.serialutil.SerialException: - printer("Serial device init failed. Ignoring this port") + printer("Serial device init failed. Ignoring this port. More info: " + traceback.format_exc()) known_devices.append(port_name) else: known_devices.append(port_name) diff --git a/tools/odrive/usbbulk_transport.py b/tools/odrive/usbbulk_transport.py index edf057e2..e1004ba8 100644 --- a/tools/odrive/usbbulk_transport.py +++ b/tools/odrive/usbbulk_transport.py @@ -6,6 +6,8 @@ import time import usb.core import usb.util import odrive.protocol +import traceback +import platform ODRIVE_VID_PID_PAIRS = [ (0x1209, 0x0D31), @@ -39,7 +41,9 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) # state where there are a few packets in a receive queue but a call # to epr.read() does not return these packet until a new packet arrives. # This undesirable queue can be cleared by resetting the device. - self.dev.reset() + # On windows this would cause file-not-found errors in subsequent dev calls + if platform.system() != 'Windows': + self.dev.reset() try: if self.dev.is_kernel_driver_active(1): @@ -185,7 +189,7 @@ def discover_channels(path, serial_number, callback, cancellation_token, printer usb_device.reset() continue else: - printer("USB device init failed. Ignoring this device") + printer("USB device init failed. Ignoring this device. More info: " + traceback.format_exc()) known_devices.append((usb_device.bus, usb_device.address)) else: known_devices.append((usb_device.bus, usb_device.address)) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 4f070b1e..7762494b 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -6,6 +6,16 @@ Liveplotter import sys import time import threading +import platform + +try: + if platform.system() == 'Windows': + import win32console + import colorama + colorama.init() +except ModuleNotFoundError: + print("Could not init terminal colors") + pass data_rate = 100 plot_rate = 10 @@ -198,27 +208,99 @@ class Logger(): Logs messages to stdout """ - COLOR_GREEN = '\x1b[92;1m' - COLOR_CYAN = '\x1b[96;1m' - COLOR_YELLOW = '\x1b[93;1m' - COLOR_RED = '\x1b[91;1m' - COLOR_RESET = '\x1b[0m' + COLOR_DEFAULT = 0 + COLOR_GREEN = 1 + COLOR_CYAN = 2 + COLOR_YELLOW = 3 + COLOR_RED = 4 + + _VT100Colors = { + COLOR_GREEN: '\x1b[92;1m', + COLOR_CYAN: '\x1b[96;1m', + COLOR_YELLOW: '\x1b[93;1m', + COLOR_RED: '\x1b[91;1m', + COLOR_DEFAULT: '\x1b[0m' + } + + _Win32Colors = { + COLOR_GREEN: 0x0A, + COLOR_CYAN: 0x0B, + COLOR_YELLOW: 0x0E, + COLOR_RED: 0x0C, + COLOR_DEFAULT: 0x07 + } def __init__(self): self._prefix = '' + self._skip_bottom_line = False # If true, messages are printed one line above the cursor + if platform.system() == 'Windows': + self._stdout_buf = win32console.GetStdHandle(win32console.STD_OUTPUT_HANDLE) def indent(self, prefix=' '): indented_logger = Logger() indented_logger._prefix = self._prefix + prefix return indented_logger + def print_on_second_last_line(self, text, color): + """ + Prints a text on the second last line. + This can be used to print a message above the command + prompt. If the command prompt spans multiple lines + there will be glitches. + If the printed text spans multiple lines there will also + be glitches (though this could be fixed). + """ + + if platform.system() == 'Windows': + # Windows <10 doesn't understand VT100 escape codes and the colorama + # also doesn't support the specific escape codes we need so we use the + # native Win32 API. + info = self._stdout_buf.GetConsoleScreenBufferInfo() + cursor_pos = info['CursorPosition'] + scroll_rect=win32console.PySMALL_RECTType( + Left=0, Top=1, + Right=info['Window'].Right, + Bottom=cursor_pos.Y-1) + scroll_dest = win32console.PyCOORDType(scroll_rect.Left, scroll_rect.Top-1) + self._stdout_buf.ScrollConsoleScreenBuffer( + scroll_rect, scroll_rect, scroll_dest, # clipping rect is same as scroll rect + u' ', Logger._Win32Colors[color]) # fill with empty cells with the desired color attributes + line_start = win32console.PyCOORDType(0, cursor_pos.Y-1) + self._stdout_buf.WriteConsoleOutputCharacter(text, line_start) + + else: + # Assume we're in a terminal that interprets VT100 escape codes. + # TODO: test on macOS + + # Escape character sequence: + # ESC 7: store cursor position + # ESC 1A: move cursor up by one + # ESC 1S: scroll entire viewport by one + # ESC 1L: insert 1 line at cursor position + # (print text) + # ESC 8: restore old cursor position + + sys.stdout.write('\x1b7\x1b[1A\x1b[1S\x1b[1L', end='', flush=True) + sys.stdout.write(Logger._VT100Colors[color] + text + Logger._VT100Colors[Logger.COLOR_DEFAULT]) + sys.stdout.write('\x1b8', end='', flush=True) + sys.stdout.flush() + + def print_colored(self, text, color): + if self._skip_bottom_line: + self.print_on_second_last_line(text, color) + else: + # On Windows, colorama does the job of interpreting the VT100 escape sequences + sys.stdout.write(Logger._VT100Colors[color] + text + Logger._VT100Colors[Logger.COLOR_DEFAULT] + '\n') + sys.stdout.flush() + def debug(self, text): - print(self._prefix + text) + self.print_colored(self._prefix + text, Logger.COLOR_DEFAULT) def success(self, text): - print(self._prefix + Logger.COLOR_GREEN + text + Logger.COLOR_RESET) + self.print_colored(self._prefix + text, Logger.COLOR_GREEN) def info(self, text): - print(self._prefix + Logger.COLOR_CYAN + text + Logger.COLOR_RESET) + self.print_colored(self._prefix + text, Logger.COLOR_CYAN) def warn(self, text): - print(self._prefix + Logger.COLOR_YELLOW + text + Logger.COLOR_RESET) + self.print_colored(self._prefix + text, Logger.COLOR_YELLOW) def error(self, text): - print(self._prefix + Logger.COLOR_RED + text + Logger.COLOR_RESET) + # TODO: write to stderr + self.print_colored(self._prefix + text, Logger.COLOR_RED) From ef52687a377a2d03c30d4fcc5d296eaab3252933 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sun, 25 Mar 2018 15:57:50 -0700 Subject: [PATCH 023/215] make fancy terminal features work on Windows Add Windows support for the following terminal features: - colored output - output on the second last line On Unix systems, VT100 escape codes are used to achieve this, functionality however Windows <10 doesn't interpret VT100 escape codes. For normal colored output, we use the colorama module to abstract this away. To print text on the second-last line we call the appropriate Win32 API functions directly (using the win32console module). --- tools/explore_odrive.py | 31 ++------- tools/odrive/serial_transport.py | 2 +- tools/odrive/usbbulk_transport.py | 8 ++- tools/odrive/utils.py | 112 ++++++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 28 deletions(-) diff --git a/tools/explore_odrive.py b/tools/explore_odrive.py index ccc252d4..66884d0f 100755 --- a/tools/explore_odrive.py +++ b/tools/explore_odrive.py @@ -8,7 +8,7 @@ import sys import platform import threading import odrive.discovery -from odrive.utils import start_liveplotter +from odrive.utils import start_liveplotter, Logger # Flush stdout by default import functools @@ -63,27 +63,7 @@ else: ## Interactive console utils ## -COLOR_RED = '\x1b[91;1m' -COLOR_CYAN = '\x1b[96;1m' -COLOR_RESET = '\x1b[0m' - -def print_on_second_last_line(text, **kwargs): - """ - Prints a text on the second last line. - This can be used to print a message above the command - prompt. If the command prompt spans multiple lines, - there will be glitches. - """ - # Escape character sequence: - # ESC 7: store cursor position - # ESC 1A: move cursor up by one - # ESC 1S: scroll entire viewport by one - # ESC 1L: insert 1 line at cursor position - # (print text) - # ESC 8: restore old cursor position - kwargs['end'] = '' - kwargs['flush'] = True - print('\x1b7\x1b[1A\x1b[1S\x1b[1L' + text + '\x1b8', **kwargs) +logger = Logger() def print_banner(): print('ODrive control utility v0.4') @@ -135,7 +115,7 @@ def did_discover_device(odrive): # Publish new ODrive to interactive console interactive_variables[interactive_name] = odrive globals()[interactive_name] = odrive # Add to globals so tab complete works - print_on_second_last_line(COLOR_CYAN + "{} to ODrive {:012X} as {}".format(verb, serial_number, interactive_name) + COLOR_RESET) + logger.info("{} to ODrive {:012X} as {}".format(verb, serial_number, interactive_name)) # Subscribe to disappearance of the device odrive.__channel__._channel_broken.subscribe(lambda: did_lose_device(interactive_name)) @@ -146,7 +126,7 @@ def did_lose_device(interactive_name): a message. """ if not app_shutdown_token.is_set(): - print_on_second_last_line(COLOR_RED + "Oh no {} disappeared".format(interactive_name) + COLOR_RESET) + logger.warn("Oh no {} disappeared".format(interactive_name)) # Connect to device printer("Waiting for device...") @@ -183,7 +163,7 @@ else: # Enable tab complete if possible try: import rlcompleter - import readline + import readline # Works only on Unix readline.parse_and_bind("tab: complete") except: sudo_prefix = "" if platform.system() == "Windows" else "sudo " @@ -206,5 +186,6 @@ console.runcode('sys.excepthook=newexcepthook') # Launch shell print_banner() +logger._skip_bottom_line = True interact() app_shutdown_token.set() diff --git a/tools/odrive/serial_transport.py b/tools/odrive/serial_transport.py index 53ca6850..dd595c6d 100644 --- a/tools/odrive/serial_transport.py +++ b/tools/odrive/serial_transport.py @@ -89,7 +89,7 @@ def discover_channels(path, serial_number, callback, cancellation_token, printer input_stream, output_stream, printer) channel.serial_device = serial_device except serial.serialutil.SerialException: - printer("Serial device init failed. Ignoring this port") + printer("Serial device init failed. Ignoring this port. More info: " + traceback.format_exc()) known_devices.append(port_name) else: known_devices.append(port_name) diff --git a/tools/odrive/usbbulk_transport.py b/tools/odrive/usbbulk_transport.py index edf057e2..e1004ba8 100644 --- a/tools/odrive/usbbulk_transport.py +++ b/tools/odrive/usbbulk_transport.py @@ -6,6 +6,8 @@ import time import usb.core import usb.util import odrive.protocol +import traceback +import platform ODRIVE_VID_PID_PAIRS = [ (0x1209, 0x0D31), @@ -39,7 +41,9 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) # state where there are a few packets in a receive queue but a call # to epr.read() does not return these packet until a new packet arrives. # This undesirable queue can be cleared by resetting the device. - self.dev.reset() + # On windows this would cause file-not-found errors in subsequent dev calls + if platform.system() != 'Windows': + self.dev.reset() try: if self.dev.is_kernel_driver_active(1): @@ -185,7 +189,7 @@ def discover_channels(path, serial_number, callback, cancellation_token, printer usb_device.reset() continue else: - printer("USB device init failed. Ignoring this device") + printer("USB device init failed. Ignoring this device. More info: " + traceback.format_exc()) known_devices.append((usb_device.bus, usb_device.address)) else: known_devices.append((usb_device.bus, usb_device.address)) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index a8c0e680..19cde1a8 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -6,6 +6,16 @@ Liveplotter import sys import time import threading +import platform + +try: + if platform.system() == 'Windows': + import win32console + import colorama + colorama.init() +except ModuleNotFoundError: + print("Could not init terminal colors") + pass data_rate = 100 plot_rate = 10 @@ -154,3 +164,105 @@ def wait_any(*events, timeout=None): if events[i].is_set(): return i raise TimeoutException() + +class Logger(): + """ + Logs messages to stdout + """ + + COLOR_DEFAULT = 0 + COLOR_GREEN = 1 + COLOR_CYAN = 2 + COLOR_YELLOW = 3 + COLOR_RED = 4 + + _VT100Colors = { + COLOR_GREEN: '\x1b[92;1m', + COLOR_CYAN: '\x1b[96;1m', + COLOR_YELLOW: '\x1b[93;1m', + COLOR_RED: '\x1b[91;1m', + COLOR_DEFAULT: '\x1b[0m' + } + + _Win32Colors = { + COLOR_GREEN: 0x0A, + COLOR_CYAN: 0x0B, + COLOR_YELLOW: 0x0E, + COLOR_RED: 0x0C, + COLOR_DEFAULT: 0x07 + } + + def __init__(self): + self._prefix = '' + self._skip_bottom_line = False # If true, messages are printed one line above the cursor + if platform.system() == 'Windows': + self._stdout_buf = win32console.GetStdHandle(win32console.STD_OUTPUT_HANDLE) + + def indent(self, prefix=' '): + indented_logger = Logger() + indented_logger._prefix = self._prefix + prefix + return indented_logger + + def print_on_second_last_line(self, text, color): + """ + Prints a text on the second last line. + This can be used to print a message above the command + prompt. If the command prompt spans multiple lines + there will be glitches. + If the printed text spans multiple lines there will also + be glitches (though this could be fixed). + """ + + if platform.system() == 'Windows': + # Windows <10 doesn't understand VT100 escape codes and the colorama + # also doesn't support the specific escape codes we need so we use the + # native Win32 API. + info = self._stdout_buf.GetConsoleScreenBufferInfo() + cursor_pos = info['CursorPosition'] + scroll_rect=win32console.PySMALL_RECTType( + Left=0, Top=1, + Right=info['Window'].Right, + Bottom=cursor_pos.Y-1) + scroll_dest = win32console.PyCOORDType(scroll_rect.Left, scroll_rect.Top-1) + self._stdout_buf.ScrollConsoleScreenBuffer( + scroll_rect, scroll_rect, scroll_dest, # clipping rect is same as scroll rect + u' ', Logger._Win32Colors[color]) # fill with empty cells with the desired color attributes + line_start = win32console.PyCOORDType(0, cursor_pos.Y-1) + self._stdout_buf.WriteConsoleOutputCharacter(text, line_start) + + else: + # Assume we're in a terminal that interprets VT100 escape codes. + # TODO: test on macOS + + # Escape character sequence: + # ESC 7: store cursor position + # ESC 1A: move cursor up by one + # ESC 1S: scroll entire viewport by one + # ESC 1L: insert 1 line at cursor position + # (print text) + # ESC 8: restore old cursor position + + sys.stdout.write('\x1b7\x1b[1A\x1b[1S\x1b[1L', end='', flush=True) + sys.stdout.write(Logger._VT100Colors[color] + text + Logger._VT100Colors[Logger.COLOR_DEFAULT]) + sys.stdout.write('\x1b8', end='', flush=True) + sys.stdout.flush() + + def print_colored(self, text, color): + if self._skip_bottom_line: + self.print_on_second_last_line(text, color) + else: + # On Windows, colorama does the job of interpreting the VT100 escape sequences + sys.stdout.write(Logger._VT100Colors[color] + text + Logger._VT100Colors[Logger.COLOR_DEFAULT] + '\n') + sys.stdout.flush() + + def debug(self, text): + self.print_colored(self._prefix + text, Logger.COLOR_DEFAULT) + def success(self, text): + self.print_colored(self._prefix + text, Logger.COLOR_GREEN) + def info(self, text): + self.print_colored(self._prefix + text, Logger.COLOR_CYAN) + def warn(self, text): + self.print_colored(self._prefix + text, Logger.COLOR_YELLOW) + def error(self, text): + # TODO: write to stderr + self.print_colored(self._prefix + text, Logger.COLOR_RED) From 0790ed8959ee901c7a875536f7fc1055d51be314 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sun, 25 Mar 2018 16:13:46 -0700 Subject: [PATCH 024/215] don't fail if build dir doesn't exist --- tools/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/build.sh b/tools/build.sh index 7591b0e5..a953d480 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -9,8 +9,8 @@ THIS_DIR="$(dirname "$0")" cd "$THIS_DIR/../Firmware" # Write all environment variables that start with "CONFIG_" to tup.config +rm -rdf build mkdir -p build -rm build/* env | grep ^CONFIG > tup.config tup generate ./tup_build.sh bash -xe ./tup_build.sh From b894ba37f2c1f59f3bb03523f2950a1658e56f62 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sun, 25 Mar 2018 17:35:41 -0700 Subject: [PATCH 025/215] fix Unix print-on-second-last-line --- tools/odrive/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 19cde1a8..e65a5dbb 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -242,9 +242,9 @@ class Logger(): # (print text) # ESC 8: restore old cursor position - sys.stdout.write('\x1b7\x1b[1A\x1b[1S\x1b[1L', end='', flush=True) + sys.stdout.write('\x1b7\x1b[1A\x1b[1S\x1b[1L') sys.stdout.write(Logger._VT100Colors[color] + text + Logger._VT100Colors[Logger.COLOR_DEFAULT]) - sys.stdout.write('\x1b8', end='', flush=True) + sys.stdout.write('\x1b8') sys.stdout.flush() def print_colored(self, text, color): From e9ec8c6276a9b9e5a06891d68fb3f02b74bb572f Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 29 Mar 2018 21:58:41 -0700 Subject: [PATCH 026/215] combine python tools into one single odrvtool.py --- Firmware/CHANGELOG.md | 15 +- Firmware/Makefile | 2 +- Firmware/README.md | 14 +- tools/dfuse/__init__.py | 4 - tools/drv_status.py | 30 ---- tools/explore_odrive.py | 191 -------------------------- tools/liveplotter.py | 21 --- tools/{ => odrive}/dfu.py | 89 +++++------- tools/{ => odrive}/dfuse/COPYING | 0 tools/{ => odrive}/dfuse/DfuDevice.py | 0 tools/{ => odrive}/dfuse/DfuFile.py | 0 tools/{ => odrive}/dfuse/DfuState.py | 0 tools/{ => odrive}/dfuse/DfuStatus.py | 0 tools/odrive/dfuse/__init__.py | 4 + tools/odrive/enums.py | 11 ++ tools/odrive/shell.py | 135 ++++++++++++++++++ tools/odrive/utils.py | 44 +++++- tools/{demo.py => odrive_demo.py} | 0 tools/odrvtool | 119 ++++++++++++++++ tools/rate_test.py | 25 ---- 20 files changed, 365 insertions(+), 339 deletions(-) delete mode 100644 tools/dfuse/__init__.py delete mode 100755 tools/drv_status.py delete mode 100755 tools/explore_odrive.py delete mode 100755 tools/liveplotter.py rename tools/{ => odrive}/dfu.py (81%) rename tools/{ => odrive}/dfuse/COPYING (100%) rename tools/{ => odrive}/dfuse/DfuDevice.py (100%) rename tools/{ => odrive}/dfuse/DfuFile.py (100%) rename tools/{ => odrive}/dfuse/DfuState.py (100%) rename tools/{ => odrive}/dfuse/DfuStatus.py (100%) create mode 100644 tools/odrive/dfuse/__init__.py create mode 100644 tools/odrive/enums.py create mode 100644 tools/odrive/shell.py rename tools/{demo.py => odrive_demo.py} (100%) create mode 100755 tools/odrvtool delete mode 100755 tools/rate_test.py diff --git a/Firmware/CHANGELOG.md b/Firmware/CHANGELOG.md index 2c636c12..28b7dfa4 100644 --- a/Firmware/CHANGELOG.md +++ b/Firmware/CHANGELOG.md @@ -7,6 +7,14 @@ Please add a note of your changes below this heading if you make a Pull Request. ### Changed * The DFU script now verifies the flash after writing + * Refactor python tools + * The scripts `explore_odrive.py`, `liveplotter.py`, `drv_status.py` and `rate_test.py` have been merged into one single `odrvtool` script. Running this script without any arguments provides the shell that `explore_odrive.py` used to provide. + * The command line options of `odrvtool` have changed compared to the original `explore_odrive.py`. See `odrvtool --help` for more details. + * `odrvtool` (previously `explore_odrive.py`) now supports controlling multiple ODrives concurrently (`odrv0`, `odrv1`, ...) + * No need to restart the `odrvtool` shell when devices get disconnected and reconnected + * ODrive accesses from within python tools are now thread-safe. That means you can read from the same remote property from multiple threads concurrently. + * The liveplotter (`odrvtool liveplotter`, formerly `liveplotter.py`) does no longer steal focus and closes as expected + * (experimental: start liveplotter from `odrvtool` shell by typing `start_liveplotter(lambda: odrv0.motor0.encoder.encoder_state)`) ### Fixed @@ -26,13 +34,6 @@ Please add a note of your changes below this heading if you make a Pull Request. * Update CubeMX generated STM platform code to version 1.19.0 * Remove `UUID_0`, `UUID_1` and `UUID_2` from USB protocol. Use `serial_number` instead. * Freertos memory pool (task stacks, etc) now uses Core Coupled Memory. -* Refactor python tools - * `explore_odrive.py` now supports controlling multiple ODrives concurrently (`odrv0`, `odrv1`, ...) - * No need to restart `explore_odrive.py` when devices get disconnected and reconnected - * The command line arguments of `explore_odrive.py` have changed. See `explore_odrive.py --help` for more details. - * ODrive accesses from within python tools are now thread-safe - * Liveplotter does no longer steal focus and closes as expected - * (experimental: start liveplotter from `explore_odrive.py` by typing `start_liveplotter(lambda: odrv0.motor0.encoder.encoder_state)`) ### Fixed * malloc now fails if we run out of memory (before it would always succeed even if we are out of ram...) diff --git a/Firmware/Makefile b/Firmware/Makefile index f4b505dc..73e1d8db 100644 --- a/Firmware/Makefile +++ b/Firmware/Makefile @@ -16,7 +16,7 @@ gdb: all arm-none-eabi-gdb $(FIRMWARE) -x openocd.gdbinit dfu: all - ../tools/dfu.py $(if $(value SERIAL_NUMBER),--serial-number $(SERIAL_NUMBER),) $(FIRMWARE_HEX) + ../tools/odrvtool $(if $(value SERIAL_NUMBER),--serial-number $(SERIAL_NUMBER),) dfu $(FIRMWARE_HEX) bmp: all arm-none-eabi-gdb --ex 'target extended-remote /dev/stlink' \ diff --git a/Firmware/README.md b/Firmware/README.md index a16c0508..7ee1181d 100644 --- a/Firmware/README.md +++ b/Firmware/README.md @@ -139,7 +139,7 @@ For working with the ODrive code you don't need an IDE, but the open-source IDE ## Communicating over USB or UART Warning: If testing USB or UART communication for the first time it is recommend that your motors are free to spin continuously and are not connected to a drivetrain with limited travel. ### From Linux/Windows/macOS -There are two example python scripts to help you get started with controlling the ODrive using python. One will drop you into an interactive shell to query settings, parameters, and variables, and let you send setpoints manually ([tools/explore_odrive.py](../tools/explore_odrive.py)). The other is a demo application to show you how to control the ODrive programmatically ([tools/demo.py](../tools/demo.py)). Below follows a step-by-step guide on how to run these. +There are two example python scripts to help you get started with controlling the ODrive using python. One will drop you into an interactive shell to query settings, parameters, and variables, and let you send setpoints manually ([tools/odrvtool](../tools/odrvtool)). The other is a demo application to show you how to control the ODrive programmatically ([tools/odrive_demo.py](../tools/odrive_demo.py)). Below follows a step-by-step guide on how to run these. * __Windows__: It is recommended to use a Unix style command prompt, such as Git Bash that comes with [Git for windows](https://git-scm.com/download/win). @@ -165,9 +165,9 @@ pip install pyusb pyserial 5. __Windows__: Use the [Zadig](http://zadig.akeo.ie/) utility to set ODrive (not STLink!) driver to libusb-win32. * If 'Odrive version 3.x' is not in the list of devices upon opening Zadig, check 'List All Devices' from the options menu. With the Odrive selected in the device list choose 'libusb-win32' from the target driver list and select the large 'install driver' button. 6. Open the bash prompt in the `ODrive/tools/` folder. -7. Run `python3 demo.py` or `python3 explore_odrive.py`. -- `demo.py` is a very simple script which will make motor 0 turn back and forth. Use this as an example if you want to control the ODrive yourself programatically. -- `explore_odrive.py` drops you into an interactive python shell where you can explore and edit the parameters that are available on your device. For instance `my_odrive.motor0.pos_setpoint = 10000` makes motor0 move to position 10000. To connect over serial instead of USB run `./tools/explore_odrive.py --path serial`. +7. Run `python3 odrive_demo.py` or `python3 odrvtool`. +- `odrive_demo.py` is a very simple script which will make motor 0 turn back and forth. Use this as an example if you want to control the ODrive yourself programatically. +- `odrvtool` drops you into an interactive python shell when started without any arguments. There you can explore and edit the parameters that are available on your device. For instance `my_odrive.motor0.pos_setpoint = 10000` makes motor0 move to position 10000. To connect over serial instead of USB run `./tools/odrvtool --path serial`. Run `./tools/odrvtool --help` to see what else you can do with the script. ### From Arduino [See ODrive Arduino Library](https://github.com/madcowswe/ODriveArduino) @@ -180,7 +180,7 @@ See the [protocol specification](protocol.md) or the [legacy protocol specificat The majority of the important parameters you would want to set after flashing the ODrive with firmware are configurable over the USB communication interface. These include some mandatory parameters that you must set for correct operation, as well as tuning and optional parameters. To start the configuration session: -* Launch `./tools/explore_odrive.py`. This will give you a command prompt where you can modify using simple assignments. +* Launch `./tools/odrvtool`. This will give you a command prompt where you can modify using simple assignments. * Configure parameters of the `my_odrive.[...].config` objects. * For example to adjust the position gain: `my_odrive.motor0.config.pos_gain = 30` Enter. * The complete list of configurable parameters is: @@ -253,7 +253,7 @@ If you have an encoder with an index (Z) signal, you may avoid having to do the * All the parameters we will be modifying are in the motor structs at the top of [MotorControl/low_level.c](MotorControl/low_level.c). * Set `.encoder.use_index = true` and `.encoder.calibrated = false`. * Flash this configuration, and let the motor scan for the index pulse and then complete the encoder calibration. -* Run `explore_odrive.py`, check [Communicating over USB or UART](#communicating-over-usb-or-uart) for instructions on how to do that. +* Run `odrvtool`, check [Communicating over USB or UART](#communicating-over-usb-or-uart) for instructions on how to do that. * Enter the following to print out the calibration parameters (substitute the motor number you are calibrating for ``): * `my_odrive.motor.encoder.encoder_offset` - This should print a number, like -326 or 1364. * `my_odrive.motor.encoder.motor_dir` - This should print 1 or -1. @@ -267,7 +267,7 @@ If you have an encoder with an index (Z) signal, you may avoid having to do the

## Checking for error codes -`explore_odrive.py`can also be used to check error codes when your odrive is not working as expected. For example `my_odrive.motor0.error` will list the error code associated with motor 0. +`odrvtool` can also be used to check error codes when your odrive is not working as expected. For example `my_odrive.motor0.error` will list the error code associated with motor 0.

The error nummber corresponds to the following: diff --git a/tools/dfuse/__init__.py b/tools/dfuse/__init__.py deleted file mode 100644 index ff023bf7..00000000 --- a/tools/dfuse/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from dfuse.DfuDevice import DfuDevice -from dfuse.DfuStatus import DfuStatus -from dfuse.DfuState import DfuState -from dfuse.DfuFile import DfuFile diff --git a/tools/drv_status.py b/tools/drv_status.py deleted file mode 100755 index bc62c3ed..00000000 --- a/tools/drv_status.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python3 -""" -Example usage of the ODrive python library to monitor and control ODrive devices -""" - -from __future__ import print_function - -import odrive.discovery -import time -import math - -# Find a connected ODrive (this will block until you connect one) -print("Waiting for ODrive...") -my_drive = odrive.discovery.find_any() -print("connected") - -# Print DRV device regs for Motor 0 -fault = my_drive.motor0.gate_driver.drv_fault -status_reg_1 = my_drive.motor0.gate_driver.status_reg_1 -status_reg_2 = my_drive.motor0.gate_driver.status_reg_2 -ctrl_reg_1 = my_drive.motor0.gate_driver.ctrl_reg_1 -ctrl_reg_2 = my_drive.motor0.gate_driver.ctrl_reg_2 - -print("DRV Fault Code: " + str(fault)) -print("Status Reg 1: " + str(status_reg_1) + " (" + format(status_reg_1, '#010b') + ")") -print("Status Reg 2: " + str(status_reg_2) + " (" + format(status_reg_2, '#010b') + ")") -print("Control Reg 1: " + str(ctrl_reg_1) + " (" + format(ctrl_reg_1, '#010b') + ")") -print("Control Reg 2: " + str(ctrl_reg_2) + " (" + format(ctrl_reg_2, '#010b') + ")") - - diff --git a/tools/explore_odrive.py b/tools/explore_odrive.py deleted file mode 100755 index 66884d0f..00000000 --- a/tools/explore_odrive.py +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env python3 -""" -Load an odrive object to play with in the IPython interactive shell. -""" - -import argparse -import sys -import platform -import threading -import odrive.discovery -from odrive.utils import start_liveplotter, Logger - -# Flush stdout by default -import functools -print = functools.partial(print, flush=True) - - -# some enums described in the README -# TODO: transmit as part of the JSON -MOTOR_TYPE_HIGH_CURRENT = 0 -#MOTOR_TYPE_LOW_CURRENT = 1 -MOTOR_TYPE_GIMBAL = 2 - -CTRL_MODE_VOLTAGE_CONTROL = 0, -CTRL_MODE_CURRENT_CONTROL = 1, -CTRL_MODE_VELOCITY_CONTROL = 2, -CTRL_MODE_POSITION_CONTROL = 3 - - -## Parse arguments ## -parser = argparse.ArgumentParser(description='Load an odrive object to play with in the IPython interactive shell.', - formatter_class=argparse.RawTextHelpFormatter) -parser.add_argument("-v", "--verbose", action="store_true", - help="print debug information") -parser.add_argument("-p", "--path", metavar="PATH", action="store", - help="The path(s) where ODrive(s) should be discovered.\n" - "By default the script will connect to any ODrive on USB.\n\n" - "To select a specific USB device:\n" - " --path usb:BUS:DEVICE\n" - "usbwhere BUS and DEVICE are the bus and device numbers as shown in `lsusb`.\n\n" - "To select a specific serial port:\n" - " --path serial:PATH\n" - "where PATH is the path of the serial port. For example \"/dev/ttyUSB0\".\n" - "You can use `ls /dev/tty*` to find the correct port.\n\n" - "You can combine USB and serial specs by separating them with a comma (no space!)\n" - "Example:\n" - " --path usb,serial:/dev/ttyUSB0\n" - "means \"discover any USB device or a serial device on /dev/ttyUSB0\"") -parser.add_argument("--no-ipython", action="store_true", - help="Use the regular Python shell\n" - "instead of the IPython shell,\n" - "even if IPython is installed\n") -parser.add_argument("-s", "--serial-number", action="store", - help="The serial number of the device. If omitted, any device is accepted.\n") -parser.set_defaults(path="usb") -args = parser.parse_args() - -if (args.verbose): - printer = print -else: - printer = lambda x: None - - -## Interactive console utils ## - -logger = Logger() - -def print_banner(): - print('ODrive control utility v0.4') - print('Please connect your ODrive.') - print('Type help() for help.') - -def print_help(): - print('') - if len(discovered_devices) == 0: - print('Connect your ODrive to {} and power it up.'.format(args.path)) - print('After that, the following message should appear:') - print(' "Connected to ODrive [serial number] as odrv0"') - print('') - print('Once the ODrive is connected, type "odrv0." and press ') - else: - print('Type "odrv0." and press ') - print('This will present you with all the properties that you can reference') - print('') - print('For example: "odrv0.motor0.encoder.pll_pos"') - print('will print the current encoder position on motor 0') - print('and "odrv0.motor0.pos_setpoint = 10000"') - print('will send motor0 to 10000') - print('') - -interactive_variables = {} -interactive_variables["help"] = print_help - - -## Device discovery ## - -discovered_devices = [] - -def did_discover_device(odrive): - """ - Handles the discovery of new devices by displaying a - message and making the device available to the interactive - console - """ - serial_number = odrive.serial_number if hasattr(odrive, 'serial_number') else "[unknown serial number]" - if serial_number in discovered_devices: - verb = "Reconnected" - index = discovered_devices.index(serial_number) - else: - verb = "Connected" - discovered_devices.append(serial_number) - index = len(discovered_devices) - 1 - interactive_name = "odrv" + str(index) - - # Publish new ODrive to interactive console - interactive_variables[interactive_name] = odrive - globals()[interactive_name] = odrive # Add to globals so tab complete works - logger.info("{} to ODrive {:012X} as {}".format(verb, serial_number, interactive_name)) - - # Subscribe to disappearance of the device - odrive.__channel__._channel_broken.subscribe(lambda: did_lose_device(interactive_name)) - -def did_lose_device(interactive_name): - """ - Handles the disappearance of a device by displaying - a message. - """ - if not app_shutdown_token.is_set(): - logger.warn("Oh no {} disappeared".format(interactive_name)) - -# Connect to device -printer("Waiting for device...") -app_shutdown_token = threading.Event() -odrive.discovery.find_all(args.path, args.serial_number, - did_discover_device, app_shutdown_token, - printer=printer) - - -## Launch interactive shell ## - -# Check if IPython is installed -if args.no_ipython: - use_ipython = False -else: - try: - import IPython - use_ipython = True - except: - print("Warning: you don't have IPython installed.") - print("If you want to have an improved interactive console with pretty colors,") - print("you should install IPython\n") - use_ipython = False - -# If IPython is installed, embed IPython shell, otherwise embed regular shell -if use_ipython: - #interactive_variables["lalala"] = print_help - help = print_help # Override help function - console = IPython.terminal.embed.InteractiveShellEmbed(local_ns=interactive_variables, banner1='') - console.runcode = console.run_code # hack to make IPython look like the regular console - interact = console -else: - - # Enable tab complete if possible - try: - import rlcompleter - import readline # Works only on Unix - readline.parse_and_bind("tab: complete") - except: - sudo_prefix = "" if platform.system() == "Windows" else "sudo " - print("Warning: could not enable tab-complete. User experience will suffer.\n" - "Run `{}pip install readline` and then restart this script to fix this." - .format(sudo_prefix)) - - import code - console = code.InteractiveConsole(locals=interactive_variables) - interact = lambda: console.interact(banner='') - -# install hook to hide ChannelBrokenException -console.runcode('import sys') -console.runcode('superexcepthook = sys.excepthook') -console.runcode('def newexcepthook(ex_class,ex,trace):\n' - ' if ex_class.__module__ + "." + ex_class.__name__ != "odrive.protocol.ChannelBrokenException":\n' - ' superexcepthook(ex_class,ex,trace)') -console.runcode('sys.excepthook=newexcepthook') - - -# Launch shell -print_banner() -logger._skip_bottom_line = True -interact() -app_shutdown_token.set() diff --git a/tools/liveplotter.py b/tools/liveplotter.py deleted file mode 100755 index 95c50c24..00000000 --- a/tools/liveplotter.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python3 -""" -Liveplotter -""" - -import time -import threading -import odrive.discovery -from odrive.utils import start_liveplotter - -data_rate = 100 -plot_rate = 10 -num_samples = 1000 - -my_odrive = odrive.discovery.find_any() - -# If you want to plot different values, change them here. -# You can plot any number of values concurrently. -start_liveplotter(lambda: [my_odrive.motor0.encoder.pll_pos, - my_odrive.motor1.encoder.pll_pos]) - diff --git a/tools/dfu.py b/tools/odrive/dfu.py similarity index 81% rename from tools/dfu.py rename to tools/odrive/dfu.py index 6dc2aad7..aad9a3d8 100755 --- a/tools/dfu.py +++ b/tools/odrive/dfu.py @@ -11,13 +11,9 @@ import platform import struct import array import fractions -import dfuse import usb.core import odrive.discovery - -# We are interactively printing status messages, so flush by default -import functools -print = functools.partial(print, flush=True) +from odrive.dfuse import * try: from intelhex import IntelHex @@ -93,28 +89,28 @@ def populate_sectors(sectors, hexfile): def set_alternate_safe(dfudev, alt): dfudev.set_alternate(alt) - if dfudev.get_state() == dfuse.DfuState.DFU_ERROR: + if dfudev.get_state() == DfuState.DFU_ERROR: dfudev.clear_status() - dfudev.wait_while_state(dfuse.DfuState.DFU_ERROR) + dfudev.wait_while_state(DfuState.DFU_ERROR) #def clear_error(dfudev) -def set_address_safe(dfudef, addr): +def set_address_safe(dfudev, addr): dfudev.set_address(addr) - status = dfudev.wait_while_state(dfuse.DfuState.DFU_DOWNLOAD_BUSY) - if status[1] != dfuse.DfuState.DFU_DOWNLOAD_IDLE: + status = dfudev.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY) + if status[1] != DfuState.DFU_DOWNLOAD_IDLE: raise RuntimeError("An error occured. Device Status: %r" % status) # take device out of DFU_DOWNLOAD_SYNC and into DFU_IDLE dfudev.abort() - status = dfudev.wait_while_state(dfuse.DfuState.DFU_DOWNLOAD_SYNC) - if status[1] != dfuse.DfuState.DFU_IDLE: + status = dfudev.wait_while_state(DfuState.DFU_DOWNLOAD_SYNC) + if status[1] != DfuState.DFU_IDLE: raise RuntimeError("An error occured. Device Status: %r" % status) def erase(dfudev, sector): set_alternate_safe(dfudev, sector['alt']) dfudev.erase(sector['addr']) - status = dfudev.wait_while_state(dfuse.DfuState.DFU_DOWNLOAD_BUSY, timeout=sector['len']/32) - if status[1] != dfuse.DfuState.DFU_DOWNLOAD_IDLE: + status = dfudev.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY, timeout=sector['len']/32) + if status[1] != DfuState.DFU_DOWNLOAD_IDLE: raise RuntimeError("An error occured. Device Status: %r" % status) def flash(dfudev, sector, data): @@ -128,8 +124,8 @@ def flash(dfudev, sector, data): #print('write to {:08X} ({} bytes)'.format( # sector['addr'] + blocknum * TRANSFER_SIZE, len(block))) dfudev.write(blocknum, block) - status = dfudev.wait_while_state(dfuse.DfuState.DFU_DOWNLOAD_BUSY) - if status[1] != dfuse.DfuState.DFU_DOWNLOAD_IDLE: + status = dfudev.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY) + if status[1] != DfuState.DFU_DOWNLOAD_IDLE: raise RuntimeError("An error occured. Device Status: %r" % status) def read(dfudev, sector): @@ -168,13 +164,13 @@ def get_first_mismatch_index(array1, array2): def jump_to_application(dfudev, address): set_address_safe(dfudev, address) #dfudev.set_address(address) - #status = dfudev.wait_while_state(dfuse.DfuState.DFU_DOWNLOAD_BUSY) - #if status[1] != dfuse.DfuState.DFU_DOWNLOAD_IDLE: + #status = dfudev.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY) + #if status[1] != DfuState.DFU_DOWNLOAD_IDLE: # raise RuntimeError("An error occured. Device Status: {}".format(status[1])) dfudev.leave() - status = dfudev.wait_while_state(dfuse.DfuState.DFU_MANIFEST_SYNC) - if status[1] != dfuse.DfuState.DFU_MANIFEST: + status = dfudev.wait_while_state(DfuState.DFU_MANIFEST_SYNC) + if status[1] != DfuState.DFU_MANIFEST: raise RuntimeError("An error occured. Device Status: {}".format(status[1])) @@ -229,55 +225,47 @@ def put_odrive_into_dfu_mode(my_drive): "Use the Zadig utility to set the driver of 'STM32 BOOTLOADER' to libusb-win32.", find_odrive_cancellation_token) -### BEGINNING OF APPLICATION ### +def launch_dfu(args, app_shutdown_token): + """ + Waits for a device that matches args.path and args.serial_number + and then upgrades the device's firmware. + """ -# parse arguments -parser = argparse.ArgumentParser(description="Program an STM32 in DFU mode. The device can be identified either by it's serial number or UUID." - "You can list all connected devices by running" - "(lsusb -d 1209:0d32 -v; lsusb -d 0483:df11 -v) | grep iSerial") -parser.add_argument("-v", "--verbose", action="store_true", - help="print debug information") -parser.add_argument('file', metavar='HEX', help='the .hex file to be flashed') -parser.add_argument("-u", "--uuid", - help="The 12-byte UUID of the device. This is a hexadecimal number of the format" - "00000000-00000000-00000000") -parser.add_argument("-s", "--serial-number", - help="The 12-digit serial number of the device. This is a string consisting of 12 upper case hexadecimal digits as displayed in lsusb" - "example: 385F324D3037") -args = parser.parse_args() + # load hex file + # TODO: Either use the elf format or pack a custom format with a manifest. + # This way we can for instance verify the target board version and only + # have to publish one file for every board. + hexfile = IntelHex(args.file) -# load hex file -hexfile = IntelHex(args.file) + if (args.verbose): + print("Contiguous segments in hex file:") + for start, end in hexfile.segments(): + print(" {:08X} to {:08X}".format(start, end - 1)) -#print("Contiguous segments in hex file:") -#for start, end in hexfile.segments(): -# print(" {:08X} to {:08X}".format(start, end - 1)) + serial_number = args.serial_number -serial_number = args.serial_number + find_odrive_cancellation_token = threading.Event() + app_shutdown_token.subscribe(lambda: find_odrive_cancellation_token.set()) - -app_cancellation_token = threading.Event() -find_odrive_cancellation_token = threading.Event() -try: print("Waiting for ODrive...") # Scan for ODrives not in DFU mode and put them into DFU mode once they appear # We only scan on USB because DFU is only possible over USB - odrive.discovery.find_all("usb", serial_number, put_odrive_into_dfu_mode, find_odrive_cancellation_token) + odrive.discovery.find_all(args.path, serial_number, put_odrive_into_dfu_mode, find_odrive_cancellation_token) # Poll libUSB until a device in DFU mode is found - while not app_cancellation_token.is_set(): + while not app_shutdown_token.is_set(): params = {} if serial_number == None else {'serial_number': serial_number} stm_device = usb.core.find(idVendor=0x0483, idProduct=0xdf11, **params) if stm_device != None: break time.sleep(1) find_odrive_cancellation_token.set() # we don't need this thread anymore - if app_cancellation_token.is_set(): + if app_shutdown_token.is_set(): sys.exit(1) print("Found device {} in DFU mode".format(stm_device.serial_number)) - dfudev = dfuse.DfuDevice(stm_device) + dfudev = DfuDevice(stm_device) sectors = list(get_device_sectors(dfudev)) @@ -344,8 +332,7 @@ try: # Jump to application jump_to_application(dfudev, 0x08000000) -finally: - find_odrive_cancellation_token.set() + # Note: the flashed image can be verified using: (0x12000 is the number of bytes to read) diff --git a/tools/dfuse/COPYING b/tools/odrive/dfuse/COPYING similarity index 100% rename from tools/dfuse/COPYING rename to tools/odrive/dfuse/COPYING diff --git a/tools/dfuse/DfuDevice.py b/tools/odrive/dfuse/DfuDevice.py similarity index 100% rename from tools/dfuse/DfuDevice.py rename to tools/odrive/dfuse/DfuDevice.py diff --git a/tools/dfuse/DfuFile.py b/tools/odrive/dfuse/DfuFile.py similarity index 100% rename from tools/dfuse/DfuFile.py rename to tools/odrive/dfuse/DfuFile.py diff --git a/tools/dfuse/DfuState.py b/tools/odrive/dfuse/DfuState.py similarity index 100% rename from tools/dfuse/DfuState.py rename to tools/odrive/dfuse/DfuState.py diff --git a/tools/dfuse/DfuStatus.py b/tools/odrive/dfuse/DfuStatus.py similarity index 100% rename from tools/dfuse/DfuStatus.py rename to tools/odrive/dfuse/DfuStatus.py diff --git a/tools/odrive/dfuse/__init__.py b/tools/odrive/dfuse/__init__.py new file mode 100644 index 00000000..68500f04 --- /dev/null +++ b/tools/odrive/dfuse/__init__.py @@ -0,0 +1,4 @@ +from .DfuDevice import DfuDevice +from .DfuStatus import DfuStatus +from .DfuState import DfuState +from .DfuFile import DfuFile diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py new file mode 100644 index 00000000..a58404b1 --- /dev/null +++ b/tools/odrive/enums.py @@ -0,0 +1,11 @@ + +# some enums described in the README +# TODO: This is dangerous. Transmit as part of the JSON +MOTOR_TYPE_HIGH_CURRENT = 0 +#MOTOR_TYPE_LOW_CURRENT = 1 +MOTOR_TYPE_GIMBAL = 2 + +CTRL_MODE_VOLTAGE_CONTROL = 0, +CTRL_MODE_CURRENT_CONTROL = 1, +CTRL_MODE_VELOCITY_CONTROL = 2, +CTRL_MODE_POSITION_CONTROL = 3 diff --git a/tools/odrive/shell.py b/tools/odrive/shell.py new file mode 100644 index 00000000..44026797 --- /dev/null +++ b/tools/odrive/shell.py @@ -0,0 +1,135 @@ + +import sys +import platform +import threading +import odrive.discovery +from odrive.utils import start_liveplotter +from odrive.enums import * + +def print_banner(): + print('ODrive control utility v0.4') + print('Please connect your ODrive.') + print('Type help() for help.') + +def print_help(): + print('') + if len(discovered_devices) == 0: + print('Connect your ODrive to {} and power it up.'.format(args.path)) + print('After that, the following message should appear:') + print(' "Connected to ODrive [serial number] as odrv0"') + print('') + print('Once the ODrive is connected, type "odrv0." and press ') + else: + print('Type "odrv0." and press ') + print('This will present you with all the properties that you can reference') + print('') + print('For example: "odrv0.motor0.encoder.pll_pos"') + print('will print the current encoder position on motor 0') + print('and "odrv0.motor0.pos_setpoint = 10000"') + print('will send motor0 to 10000') + print('') + + +interactive_variables = {} +interactive_variables["help"] = print_help + +discovered_devices = [] + +def did_discover_device(odrive, logger, app_shutdown_token): + """ + Handles the discovery of new devices by displaying a + message and making the device available to the interactive + console + """ + serial_number = odrive.serial_number if hasattr(odrive, 'serial_number') else "[unknown serial number]" + if serial_number in discovered_devices: + verb = "Reconnected" + index = discovered_devices.index(serial_number) + else: + verb = "Connected" + discovered_devices.append(serial_number) + index = len(discovered_devices) - 1 + interactive_name = "odrv" + str(index) + + # Publish new ODrive to interactive console + interactive_variables[interactive_name] = odrive + globals()[interactive_name] = odrive # Add to globals so tab complete works + logger.info("{} to ODrive {:012X} as {}".format(verb, serial_number, interactive_name)) + + # Subscribe to disappearance of the device + odrive.__channel__._channel_broken.subscribe(lambda: did_lose_device(interactive_name, logger, app_shutdown_token)) + +def did_lose_device(interactive_name, logger, app_shutdown_token): + """ + Handles the disappearance of a device by displaying + a message. + """ + if not app_shutdown_token.is_set(): + logger.warn("Oh no {} disappeared".format(interactive_name)) + +def launch_shell(args, logger, printer, app_shutdown_token): + """ + Launches an interactive python or IPython command line + interface. + As ODrives are connected they are made available as + "odrv0", "odrv1", ... + """ + + # Connect to device + logger.debug("Waiting for device...") + odrive.discovery.find_all(args.path, args.serial_number, + lambda dev: did_discover_device(dev, logger, app_shutdown_token), + app_shutdown_token, + printer=printer) + + # Check if IPython is installed + if args.no_ipython: + use_ipython = False + else: + try: + import IPython + use_ipython = True + except: + print("Warning: you don't have IPython installed.") + print("If you want to have an improved interactive console with pretty colors,") + print("you should install IPython\n") + use_ipython = False + + # If IPython is installed, embed IPython shell, otherwise embed regular shell + if use_ipython: + #interactive_variables["lalala"] = print_help + help = print_help # Override help function + console = IPython.terminal.embed.InteractiveShellEmbed(local_ns=interactive_variables, banner1='') + console.runcode = console.run_code # hack to make IPython look like the regular console + interact = console + else: + + # Enable tab complete if possible + try: + import rlcompleter + import readline # Works only on Unix + readline.parse_and_bind("tab: complete") + except: + sudo_prefix = "" if platform.system() == "Windows" else "sudo " + print("Warning: could not enable tab-complete. User experience will suffer.\n" + "Run `{}pip install readline` and then restart this script to fix this." + .format(sudo_prefix)) + + import code + console = code.InteractiveConsole(locals=interactive_variables) + interact = lambda: console.interact(banner='') + + # install hook to hide ChannelBrokenException + console.runcode('import sys') + console.runcode('superexcepthook = sys.excepthook') + console.runcode('def newexcepthook(ex_class,ex,trace):\n' + ' if ex_class.__module__ + "." + ex_class.__name__ != "odrive.protocol.ChannelBrokenException":\n' + ' superexcepthook(ex_class,ex,trace)') + console.runcode('sys.excepthook=newexcepthook') + + + # Launch shell + print_banner() + logger._skip_bottom_line = True + interact() + app_shutdown_token.set() diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index e65a5dbb..28e2c830 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -72,6 +72,44 @@ def start_liveplotter(get_var_callback): threading.Thread(target=plot_data).start() #plot_data() +def print_drv_regs(device): + """ + Dumps the current gate driver regisers for Motor 0 + """ + fault = device.motor0.gate_driver.drv_fault + status_reg_1 = device.motor0.gate_driver.status_reg_1 + status_reg_2 = device.motor0.gate_driver.status_reg_2 + ctrl_reg_1 = device.motor0.gate_driver.ctrl_reg_1 + ctrl_reg_2 = device.motor0.gate_driver.ctrl_reg_2 + + print("DRV Fault Code: " + str(fault)) + print("Status Reg 1: " + str(status_reg_1) + " (" + format(status_reg_1, '#010b') + ")") + print("Status Reg 2: " + str(status_reg_2) + " (" + format(status_reg_2, '#010b') + ")") + print("Control Reg 1: " + str(ctrl_reg_1) + " (" + format(ctrl_reg_1, '#010b') + ")") + print("Control Reg 2: " + str(ctrl_reg_2) + " (" + format(ctrl_reg_2, '#010b') + ")") + +def rate_test(device): + """ + Tests how many integers per second can be transmitted + """ + + import matplotlib.pyplot as plt + plt.ion() + + print("reading 10000 values...") + numFrames = 10000 + vals = [] + for _ in range(numFrames): + vals.append(device.motor0.loop_counter) + + plt.plot(vals) + + loopsPerFrame = (vals[-1] - vals[0])/numFrames + loopsPerSec = (168000000/(2*10192)) + FramePerSec = loopsPerSec/loopsPerFrame + print("Frames per second: " + str(FramePerSec)) + + ## Exceptions ## class TimeoutException(Exception): @@ -192,9 +230,10 @@ class Logger(): COLOR_DEFAULT: 0x07 } - def __init__(self): + def __init__(self, verbose=True): self._prefix = '' self._skip_bottom_line = False # If true, messages are printed one line above the cursor + self._verbose = verbose if platform.system() == 'Windows': self._stdout_buf = win32console.GetStdHandle(win32console.STD_OUTPUT_HANDLE) @@ -256,7 +295,8 @@ class Logger(): sys.stdout.flush() def debug(self, text): - self.print_colored(self._prefix + text, Logger.COLOR_DEFAULT) + if self._verbose: + self.print_colored(self._prefix + text, Logger.COLOR_DEFAULT) def success(self, text): self.print_colored(self._prefix + text, Logger.COLOR_GREEN) def info(self, text): diff --git a/tools/demo.py b/tools/odrive_demo.py similarity index 100% rename from tools/demo.py rename to tools/odrive_demo.py diff --git a/tools/odrvtool b/tools/odrvtool new file mode 100755 index 00000000..d8d31bda --- /dev/null +++ b/tools/odrvtool @@ -0,0 +1,119 @@ +#!/usr/bin/env python3 +""" +ODrive command line utility +""" + +import argparse +import odrive.discovery +from odrive.utils import Logger, Event + +# Flush stdout by default +import functools +print = functools.partial(print, flush=True) + + +## Parse arguments ## +parser = argparse.ArgumentParser(description='ODrive command line utility\n' + 'Running this tool without any arguments is equivalent to running `odrvtool shell`\n', + formatter_class=argparse.RawTextHelpFormatter) + +# Subcommands +subparsers = parser.add_subparsers(help='sub-command help', dest='command') +shell_parser = subparsers.add_parser('shell', help='Drop into an interactive python shell that lets you interact with the ODrive(s)') +shell_parser.add_argument("--no-ipython", action="store_true", + help="Use the regular Python shell " + "instead of the IPython shell, " + "even if IPython is installed.") + +dfu_parser = subparsers.add_parser('dfu', help="Upgrade the ODrive device firmware") +dfu_parser.add_argument('file', metavar='HEX', help='The .hex file to be flashed. Make sure your firmware board version matches the actual board version.') + +subparsers.add_parser('liveplotter', help="Upgrade the ODrive's Firmware") +subparsers.add_parser('drv-status', help="Show status of the on-board DRV8301 chips (for debugging only)") +subparsers.add_parser('rate-test', help="Estimate the average transmission bandwidth over USB") + +# General arguments +parser.add_argument("-p", "--path", metavar="PATH", action="store", + help="The path(s) where ODrive(s) should be discovered.\n" + "By default the script will connect to any ODrive on USB.\n\n" + "To select a specific USB device:\n" + " --path usb:BUS:DEVICE\n" + "usbwhere BUS and DEVICE are the bus and device numbers as shown in `lsusb`.\n\n" + "To select a specific serial port:\n" + " --path serial:PATH\n" + "where PATH is the path of the serial port. For example \"/dev/ttyUSB0\".\n" + "You can use `ls /dev/tty*` to find the correct port.\n\n" + "You can combine USB and serial specs by separating them with a comma (no space!)\n" + "Example:\n" + " --path usb,serial:/dev/ttyUSB0\n" + "means \"discover any USB device or a serial device on /dev/ttyUSB0\"") +parser.add_argument("-s", "--serial-number", action="store", + help="The 12-digit serial number of the device. " + "This is a string consisting of 12 upper case hexadecimal " + "digits as displayed in lsusb. \n" + " example: 385F324D3037\n" + "You can list all devices connected to USB by running\n" + "(lsusb -d 1209:0d32 -v; lsusb -d 0483:df11 -v) | grep iSerial\n" + "If omitted, any device is accepted.") +parser.add_argument("-v", "--verbose", action="store_true", + help="print debug information") + +parser.set_defaults(path="usb") +args = parser.parse_args() + +# Default command +if args.command is None: + args.command = 'shell' + args.no_ipython = False + +# We are interactively printing status messages, so flush by default +import functools +print = functools.partial(print, flush=True) + +# TODO: deprecate printer - use logger instead +if (args.verbose): + printer = print +else: + printer = lambda x: None + +logger = Logger(verbose=args.verbose) +logger.debug(str(args)) + +app_shutdown_token = Event() + +try: + if args.command == 'shell': + import odrive.shell + odrive.shell.launch_shell(args, logger, printer, app_shutdown_token) + + elif args.command == 'dfu': + import odrive.dfu + odrive.dfu.launch_dfu(args, app_shutdown_token) + + elif args.command == 'liveplotter': + from odrive.utils import start_liveplotter + print("Waiting for ODrive...") + my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number) + + # If you want to plot different values, change them here. + # You can plot any number of values concurrently. + start_liveplotter(lambda: [my_odrive.motor0.encoder.pll_pos, + my_odrive.motor1.encoder.pll_pos]) + + elif args.command == 'drv-status': + from odrive.utils import print_drv_regs + print("Waiting for ODrive...") + my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number) + print_drv_regs(my_odrive) + + elif args.command == 'rate-test': + from odrive.utils import rate_test + print("Waiting for ODrive...") + my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number) + rate_test(my_odrive) + + else: + raise Exception("unknown command: " + args.command) + +finally: + app_shutdown_token.set() diff --git a/tools/rate_test.py b/tools/rate_test.py deleted file mode 100755 index 76f359a6..00000000 --- a/tools/rate_test.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env python3 - -import time -import odrive.discovery -import matplotlib.pyplot as plt -import numpy as np - -# Find a connected ODrive (this will block until you connect one) -print("Waiting for ODrive...") -myOdrive = odrive.discovery.find_any() -print("connected") - -plt.ion() - -numFrames = 10000 -vals = [] -for _ in range(numFrames): - vals.append(myOdrive.motor0.loop_counter) - -plt.plot(vals) - -loopsPerFrame = (vals[-1] - vals[0])/numFrames -loopsPerSec = (168000000/(2*10192)) -FramePerSec = loopsPerSec/loopsPerFrame -print(FramePerSec) \ No newline at end of file From 9fb5fceea71c61cb62cd42c08ca5032bf71d3855 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 29 Mar 2018 23:18:40 -0700 Subject: [PATCH 027/215] change all occurrences of my_odrive in the README to odrv0 --- Firmware/CHANGELOG.md | 2 +- Firmware/README.md | 46 +++++++++++++++++++++---------------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Firmware/CHANGELOG.md b/Firmware/CHANGELOG.md index 28b7dfa4..c046e00f 100644 --- a/Firmware/CHANGELOG.md +++ b/Firmware/CHANGELOG.md @@ -25,7 +25,7 @@ Please add a note of your changes below this heading if you make a Pull Request. ### Added * **Storing of configuration parameters to Non Volatile Memory** * **USB Bootloader** -* `make erase_config` to erase the configuration with an STLink (the configuration can also be erased from within explore_odrive.py, using `my_odrive.erase_configuration()`) +* `make erase_config` to erase the configuration with an STLink (the configuration can also be erased from within explore_odrive.py, using `odrv0.erase_configuration()`) * Travis-CI builds firmware for all board versions and deploys the binaries when a tag is pushed to master ### Changed diff --git a/Firmware/README.md b/Firmware/README.md index 7ee1181d..cd4a677d 100644 --- a/Firmware/README.md +++ b/Firmware/README.md @@ -167,7 +167,7 @@ pip install pyusb pyserial 6. Open the bash prompt in the `ODrive/tools/` folder. 7. Run `python3 odrive_demo.py` or `python3 odrvtool`. - `odrive_demo.py` is a very simple script which will make motor 0 turn back and forth. Use this as an example if you want to control the ODrive yourself programatically. -- `odrvtool` drops you into an interactive python shell when started without any arguments. There you can explore and edit the parameters that are available on your device. For instance `my_odrive.motor0.pos_setpoint = 10000` makes motor0 move to position 10000. To connect over serial instead of USB run `./tools/odrvtool --path serial`. Run `./tools/odrvtool --help` to see what else you can do with the script. +- `odrvtool` drops you into an interactive python shell when started without any arguments. There you can explore and edit the parameters that are available on your device. For instance `odrv0.motor0.pos_setpoint = 10000` makes motor0 move to position 10000. To connect over serial instead of USB run `./tools/odrvtool --path serial`. Run `./tools/odrvtool --help` to see what else you can do with the script. ### From Arduino [See ODrive Arduino Library](https://github.com/madcowswe/ODriveArduino) @@ -181,26 +181,26 @@ The majority of the important parameters you would want to set after flashing th To start the configuration session: * Launch `./tools/odrvtool`. This will give you a command prompt where you can modify using simple assignments. -* Configure parameters of the `my_odrive.[...].config` objects. - * For example to adjust the position gain: `my_odrive.motor0.config.pos_gain = 30` Enter. +* Configure parameters of the `odrv0.[...].config` objects. + * For example to adjust the position gain: `odrv0.motor0.config.pos_gain = 30` Enter. * The complete list of configurable parameters is: - * `my_odrive.motorN.config.*` - * `my_odrive.axisN.config.*` + * `odrv0.motorN.config.*` + * `odrv0.axisN.config.*` * where N is a valid motor number (0 or 1). -* Save the configuration into non-volatile memory: `my_odrive.save_configuration()` Enter +* Save the configuration into non-volatile memory: `odrv0.save_configuration()` Enter * This will save the properties of all the `[...].config` objects and no other parameters. -* Reboot the drive: `my_odrive.reboot()` Enter +* Reboot the drive: `odrv0.reboot()` Enter -Note that a firmware upgrade at this point will preserve the configuration if and only if the parameters of both firmware versions are identical. Should you need to reset the configuration, you can run `my_odrive.erase_configuration()`. +Note that a firmware upgrade at this point will preserve the configuration if and only if the parameters of both firmware versions are identical. Should you need to reset the configuration, you can run `odrv0.erase_configuration()`. __Developers__: Be aware that you can also modify the compile-time defaults for all of these parameters. Most of them you will find at the top of [MotorControl/low_level.c](MotorControl/low_level.c#L50). Note that the configuration parameters there are somewhat intertwined with runtime variables and hardware specific configuration that should not be changed. Also note that all parameters occur twice. ### Mandatory parameters You must set for every motor: -* `my_odrive.motorN.encoder.config.cpr`: Encoder Count Per Revolution (CPR). This is 4x the Pulse Per Revolution (PPR) value. -* `my_odrive.motorN.config.pole_pairs`: This is the number of magnet poles in the rotor, **divided by two**. You can simply count the number of permanent magnets in the rotor, if you can see them. Note: this is not the same as the number of coils in the stator. -* `my_odrive.config.brake_resistance` [Ohm]: This is the resistance of the brake resistor. If you are not using it, you may set it to 0.0f. -* `my_odrive.motorN.config.motor_type`: This is the type of motor being used. Currently two types of motors are supported -- High-current motors (`MOTOR_TYPE_HIGH_CURRENT`) and Gimbal motors (`MOTOR_TYPE_GIMBAL`). +* `odrv0.motorN.encoder.config.cpr`: Encoder Count Per Revolution (CPR). This is 4x the Pulse Per Revolution (PPR) value. +* `odrv0.motorN.config.pole_pairs`: This is the number of magnet poles in the rotor, **divided by two**. You can simply count the number of permanent magnets in the rotor, if you can see them. Note: this is not the same as the number of coils in the stator. +* `odrv0.config.brake_resistance` [Ohm]: This is the resistance of the brake resistor. If you are not using it, you may set it to 0.0f. +* `odrv0.motorN.config.motor_type`: This is the type of motor being used. Currently two types of motors are supported -- High-current motors (`MOTOR_TYPE_HIGH_CURRENT`) and Gimbal motors (`MOTOR_TYPE_GIMBAL`). #### Motor Modes If you're using a regular hobby brushless motor like [this](https://hobbyking.com/en_us/turnigy-aerodrive-sk3-5065-236kv-brushless-outrunner-motor.html) one, you should set `motor_mode` to `MOTOR_TYPE_HIGH_CURRENT`. For low-current gimbal motors like [this](https://hobbyking.com/en_us/turnigy-hd-5208-brushless-gimbal-motor-bldc.html) one, you should choose `MOTOR_TYPE_GIMBAL`. Do not use `MOTOR_TYPE_GIMBAL` on a motor that is not a gimbal motor, as it may overheat the motor or the ODrive. @@ -212,15 +212,15 @@ If 100's of mA current noise is "large" for you, and you intend to spin the moto ### Tuning parameters The most important parameters are the limits: -* The current limit: `my_odrive.motorN.current_control.config.current_lim` [A]. The default current limit, for safety reasons, is set to 10A. This is quite weak, and good for making sure the drive is stable. Once you have tuned the drive, you can increase this to 75A to get some performance. Note that above 75A, you must change the current amplifier gains. +* The current limit: `odrv0.motorN.current_control.config.current_lim` [A]. The default current limit, for safety reasons, is set to 10A. This is quite weak, and good for making sure the drive is stable. Once you have tuned the drive, you can increase this to 75A to get some performance. Note that above 75A, you must change the current amplifier gains. * Note: The motor current and the current drawn from the power supply is not the same in general. You should not look at the power supply current to see what is going on with the motor current. -* The velocity limit: `my_odrive.motorN.config.vel_limit` [counts/s]. The motor will be limited to this speed; again the default value is quite slow. -* You can change `my_odrive.motorN.config.calibration_current` [A] to the largest value you feel comfortable leaving running through the motor continously when the motor is stationary. +* The velocity limit: `odrv0.motorN.config.vel_limit` [counts/s]. The motor will be limited to this speed; again the default value is quite slow. +* You can change `odrv0.motorN.config.calibration_current` [A] to the largest value you feel comfortable leaving running through the motor continously when the motor is stationary. The motion control gains are currently manually tuned: -* `my_odrive.motorN.config.pos_gain = 20.0f` [(counts/s) / counts] -* `my_odrive.motorN.config.vel_gain = 15.0f / 10000.0f` [A/(counts/s)] -* `my_odrive.motorN.config.vel_integrator_gain = 10.0f / 10000.0f` [A/(counts/s * s)] +* `odrv0.motorN.config.pos_gain = 20.0f` [(counts/s) / counts] +* `odrv0.motorN.config.vel_gain = 15.0f / 10000.0f` [A/(counts/s)] +* `odrv0.motorN.config.vel_integrator_gain = 10.0f / 10000.0f` [A/(counts/s * s)] An upcoming feature will enable automatic tuning. Until then, here is a rough tuning procedure: * Set the integrator gain to 0 @@ -233,14 +233,14 @@ An upcoming feature will enable automatic tuning. Until then, here is a rough tu ### Optional parameters By default both motors are enabled, and the default control mode is position control. -If you want a different mode, you can change `my_odrive.motorN.config.control_mode`. +If you want a different mode, you can change `odrv0.motorN.config.control_mode`. Possible values are: * `CTRL_MODE_POSITION_CONTROL` * `CTRL_MODE_VELOCITY_CONTROL` * `CTRL_MODE_CURRENT_CONTROL` * `CTRL_MODE_VOLTAGE_CONTROL` - this one is not normally used. -To disable a motor at startup, set `my_odrive.axisN.config.enable_control` and `my_odrive.axisN.config.do_calibration` to `False`. +To disable a motor at startup, set `odrv0.axisN.config.enable_control` and `odrv0.axisN.config.do_calibration` to `False`.

## Encoder Calibration @@ -255,8 +255,8 @@ If you have an encoder with an index (Z) signal, you may avoid having to do the * Flash this configuration, and let the motor scan for the index pulse and then complete the encoder calibration. * Run `odrvtool`, check [Communicating over USB or UART](#communicating-over-usb-or-uart) for instructions on how to do that. * Enter the following to print out the calibration parameters (substitute the motor number you are calibrating for ``): - * `my_odrive.motor.encoder.encoder_offset` - This should print a number, like -326 or 1364. - * `my_odrive.motor.encoder.motor_dir` - This should print 1 or -1. + * `odrv0.motor.encoder.encoder_offset` - This should print a number, like -326 or 1364. + * `odrv0.motor.encoder.motor_dir` - This should print 1 or -1. * Copy these numbers to the corresponding entries in low_level.c: `.encoder.encoder_offset` and `.encoder.motor_dir`. * _Warning_: Please be careful to enter the correct numbers, and not to confuse the motor channels. Incorrect values may cause the motor to spin out of control. * Set `.encoder.calibrated = true`. @@ -267,7 +267,7 @@ If you have an encoder with an index (Z) signal, you may avoid having to do the

## Checking for error codes -`odrvtool` can also be used to check error codes when your odrive is not working as expected. For example `my_odrive.motor0.error` will list the error code associated with motor 0. +`odrvtool` can also be used to check error codes when your odrive is not working as expected. For example `odrv0.motor0.error` will list the error code associated with motor 0.

The error nummber corresponds to the following: From 34a95f971bd46bd3c4d965866f6ca2db2d0aa73e Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 30 Mar 2018 15:05:50 -0700 Subject: [PATCH 028/215] add get_version to python library --- tools/odrive/__init__.py | 5 +++++ tools/odrive/version.py | 46 ++++++++++++++++++++++++++++++++++++++++ tools/odrvtool | 7 +++++- 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 tools/odrive/version.py diff --git a/tools/odrive/__init__.py b/tools/odrive/__init__.py index e69de29b..6eca987b 100644 --- a/tools/odrive/__init__.py +++ b/tools/odrive/__init__.py @@ -0,0 +1,5 @@ + +# Standard convention is to add a __version__ attribute to the package +from .version import get_version +__version__ = get_version() +del get_version diff --git a/tools/odrive/version.py b/tools/odrive/version.py new file mode 100644 index 00000000..327c915c --- /dev/null +++ b/tools/odrive/version.py @@ -0,0 +1,46 @@ + +import re +import subprocess +import os +import sys + +def get_version(git_only=False): + """ + Returns the versions of the tools + If git_only is true, the version.txt file is ignored even + if it is present. + """ + script_dir = os.path.dirname(os.path.realpath(__file__)) + + # Try to read the version.txt file that is generated during + # the packaging step + version_file_path = os.path.join(script_dir, 'version.txt') + if os.path.exists(version_file_path) and git_only == False: + with open(version_file_path) as version_file: + return version_file.readline().rstrip('\n') + + try: + # Determine the current git commit version + git_result = subprocess.run(["git", "describe", "--always", "--tags", "--dirty=*"], + cwd=script_dir, + stdout=subprocess.PIPE, timeout=10) + git_tag = git_result.stdout.decode(sys.stdout.encoding) + + regex=r'.*v([0-9a-zA-Z]).([0-9a-zA-Z]).([0-9a-zA-Z])(.*)' + package_version_major = int(re.sub(regex, r"\1", git_tag)) + package_version_minor = int(re.sub(regex, r"\2", git_tag)) + package_version_revision = int(re.sub(regex, r"\3", git_tag)) + package_version_unreleased = (re.sub(regex, r"\4", git_tag) != "") + + if package_version_unreleased: + package_version_revision += 1 + + # TODO: fetch from Git describe + version = '{}.{}.{}'.format(package_version_major, package_version_minor, package_version_revision) + + if package_version_unreleased: + version += ".dev" + except Exception as ex: + print(ex) + version = "whatever version in " + script_dir + return version diff --git a/tools/odrvtool b/tools/odrvtool index d8d31bda..8d67203e 100755 --- a/tools/odrvtool +++ b/tools/odrvtool @@ -57,6 +57,8 @@ parser.add_argument("-s", "--serial-number", action="store", "If omitted, any device is accepted.") parser.add_argument("-v", "--verbose", action="store_true", help="print debug information") +parser.add_argument("--version", action="store_true", + help="print version information and exit") parser.set_defaults(path="usb") args = parser.parse_args() @@ -82,7 +84,10 @@ logger.debug(str(args)) app_shutdown_token = Event() try: - if args.command == 'shell': + if args.version == True: + print("ODrive control utility v" + odrive.__version__) + + elif args.command == 'shell': import odrive.shell odrive.shell.launch_shell(args, logger, printer, app_shutdown_token) From eeb57863977eba54498fd6234aa60c7178341513 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 30 Mar 2018 15:19:41 -0700 Subject: [PATCH 029/215] add setup.py to publish python tools to PyPi --- .gitignore | 28 -------------- Firmware/CHANGELOG.md | 1 + tools/.gitignore | 29 ++++++++++++++ tools/requirements.txt | 3 ++ tools/setup.py | 88 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+), 28 deletions(-) create mode 100644 tools/.gitignore create mode 100644 tools/requirements.txt create mode 100644 tools/setup.py diff --git a/.gitignore b/.gitignore index 4a8546b5..db493d3f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,34 +6,6 @@ __pycache__/ # C extensions *.so -# Distribution / packaging -.Python -#env/ -#build/ -#develop-eggs/ -#dist/ -#downloads/ -#eggs/ -#.eggs/ -#lib/ -#lib64/ -#parts/ -#sdist/ -#var/ -#*.egg-info/ -#.installed.cfg -#*.egg - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - # Unit test / coverage reports htmlcov/ .tox/ diff --git a/Firmware/CHANGELOG.md b/Firmware/CHANGELOG.md index c046e00f..fb823525 100644 --- a/Firmware/CHANGELOG.md +++ b/Firmware/CHANGELOG.md @@ -4,6 +4,7 @@ Please add a note of your changes below this heading if you make a Pull Request. ### Added * `make write_otp` command to burn the board version onto the ODrive's one-time programmable memory. If you have an ODrive v3.4 or older, you should run this once for a better firmware update user experience in the future. Run the command without any options for more details. Once set, the board version is exposed through the `board_version_[...]` properties. * bake Git-derived firmware version into firmware binary. The firmware version is exposed through the `fw_version_[...]` properties. + * infrastructure to publish the python tools to PyPi. See `tools/setup.py` for details. ### Changed * The DFU script now verifies the flash after writing diff --git a/tools/.gitignore b/tools/.gitignore new file mode 100644 index 00000000..8effff5e --- /dev/null +++ b/tools/.gitignore @@ -0,0 +1,29 @@ + +# Python Distribution / packaging +.Python +#env/ +#build/ +#develop-eggs/ +/dist/ +#downloads/ +#eggs/ +#.eggs/ +#lib/ +#lib64/ +#parts/ +#sdist/ +#var/ +/*.egg-info/ +#.installed.cfg +#*.egg +/MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt diff --git a/tools/requirements.txt b/tools/requirements.txt new file mode 100644 index 00000000..7d1e0773 --- /dev/null +++ b/tools/requirements.txt @@ -0,0 +1,3 @@ +--index-url https://pypi.python.org/simple/ + +-e . \ No newline at end of file diff --git a/tools/setup.py b/tools/setup.py new file mode 100644 index 00000000..27b4fdcc --- /dev/null +++ b/tools/setup.py @@ -0,0 +1,88 @@ +""" +This script is used to deploy the ODrive python tools to PyPi +so that users can install them easily with +"pip install odrive" + +To install the package and its dependencies locally, run: + sudo pip install -r requirements.txt + +To build and package the python tools into a tar archive: + python setup.py sdist + +Warning: Before you proceed, be aware that you can upload a +specific version only ever once. After that you need to increment +the hotfix number. Deleting the release manually on the PyPi +website does not help. + +Use TestPyPi while developing. + +To build, package and upload the python tools to TestPyPi, run: + python setup.py sdist upload -r pypitest +To make a real release ensure you're at the release commit +and then run the above command without the "test". + +To install a prerelease version from test index: + sudo pip install --index-url https://test.pypi.org/simple/ --no-cache-dir odrive + + +PyPi access requires that you have set up ~/.pypirc with your +PyPi credentials and that your account has the rights +to publish packages with the name odrive. +""" + +# TODO: add additional y/n prompt to prevent from erroneous upload + +from distutils.core import setup +import os +import sys + +creating_package = "sdist" in sys.argv + +# Load version from Git tag +import odrive.version +version = odrive.version.get_version(git_only=creating_package) + +# Change this if you already uploaded the current +# version but need to release a hotfix +hotfix = 0 + +if creating_package and (hotfix > 0 or not version[-1].isdigit()): + # Add this for hotfixes + version += "-" + str(hotfix) + + +# If we're currently creating the package we need to autogenerate +# a file that contains the version string +if creating_package: + version_file_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'odrive', 'version.txt') + with open(version_file_path, mode='w') as version_file: + version_file.write(version) + + +setup( + name = 'odrive', + packages = ['odrive'], # this must be the same as the name above + scripts = ['odrvtool', 'odrive_demo.py'], + version = version, + description = 'Control utilities for the ODrive high performance motor controller', + author = 'Oskar Weigl', + author_email = 'oskar.weigl@odriverobotics.com', + license='MIT', + url = 'https://github.com/madcowswe/ODrive', + keywords = ['odrive', 'motor', 'motor control'], + install_requires = [ + 'PyUSB', # Required to access USB devices from Python through libusb + 'PySerial', # Required to access serial devices from Python + 'IntelHex', # Used to by DFU to load firmware files + 'matplotlib' # Required to run the liveplotter + ], + package_data={'': ['version.txt']}, + include_package_data=True, + classifiers = [], +) + +# TODO: include README + +# clean up +if creating_package: + os.remove(version_file_path) From febdb248563b05f8116ba7773ddee4a84dd3468b Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 30 Mar 2018 16:24:33 -0700 Subject: [PATCH 030/215] display correct python tools version --- tools/odrive/shell.py | 10 ++++------ tools/odrvtool | 4 +++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/odrive/shell.py b/tools/odrive/shell.py index 44026797..9bfd39b6 100644 --- a/tools/odrive/shell.py +++ b/tools/odrive/shell.py @@ -7,11 +7,10 @@ from odrive.utils import start_liveplotter from odrive.enums import * def print_banner(): - print('ODrive control utility v0.4') print('Please connect your ODrive.') print('Type help() for help.') -def print_help(): +def print_help(args): print('') if len(discovered_devices) == 0: print('Connect your ODrive to {} and power it up.'.format(args.path)) @@ -31,7 +30,6 @@ def print_help(): interactive_variables = {} -interactive_variables["help"] = print_help discovered_devices = [] @@ -95,15 +93,15 @@ def launch_shell(args, logger, printer, app_shutdown_token): print("you should install IPython\n") use_ipython = False + interactive_variables["help"] = lambda: print_help(args) + # If IPython is installed, embed IPython shell, otherwise embed regular shell if use_ipython: - #interactive_variables["lalala"] = print_help - help = print_help # Override help function + help = lambda: print_help(args) # Override help function console = IPython.terminal.embed.InteractiveShellEmbed(local_ns=interactive_variables, banner1='') console.runcode = console.run_code # hack to make IPython look like the regular console interact = console else: - # Enable tab complete if possible try: import rlcompleter diff --git a/tools/odrvtool b/tools/odrvtool index 8d67203e..ae6f2662 100755 --- a/tools/odrvtool +++ b/tools/odrvtool @@ -81,11 +81,13 @@ else: logger = Logger(verbose=args.verbose) logger.debug(str(args)) +print("ODrive control utility v" + odrive.__version__) + app_shutdown_token = Event() try: if args.version == True: - print("ODrive control utility v" + odrive.__version__) + pass elif args.command == 'shell': import odrive.shell From 583ab2b76dc7d595b8f97c4895ac753f7630e7d5 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 30 Mar 2018 16:32:33 -0700 Subject: [PATCH 031/215] silence python warnings --- tools/odrive/shell.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/odrive/shell.py b/tools/odrive/shell.py index 9bfd39b6..21670e3d 100644 --- a/tools/odrive/shell.py +++ b/tools/odrive/shell.py @@ -4,7 +4,7 @@ import platform import threading import odrive.discovery from odrive.utils import start_liveplotter -from odrive.enums import * +from odrive.enums import * # pylint: disable=W0614 def print_banner(): print('Please connect your ODrive.') @@ -97,14 +97,13 @@ def launch_shell(args, logger, printer, app_shutdown_token): # If IPython is installed, embed IPython shell, otherwise embed regular shell if use_ipython: - help = lambda: print_help(args) # Override help function + help = lambda: print_help(args) # Override help function # pylint: disable=W0612 console = IPython.terminal.embed.InteractiveShellEmbed(local_ns=interactive_variables, banner1='') console.runcode = console.run_code # hack to make IPython look like the regular console interact = console else: # Enable tab complete if possible try: - import rlcompleter import readline # Works only on Unix readline.parse_and_bind("tab: complete") except: From fbaac861d73904626cb6bf77f60e4fca89cb9a0e Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 30 Mar 2018 16:46:22 -0700 Subject: [PATCH 032/215] rename odrvtool to odrivetool --- Firmware/CHANGELOG.md | 12 ++++++------ Firmware/Makefile | 2 +- Firmware/README.md | 12 ++++++------ tools/odrvtool | 2 +- tools/setup.py | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Firmware/CHANGELOG.md b/Firmware/CHANGELOG.md index fb823525..c8c4662b 100644 --- a/Firmware/CHANGELOG.md +++ b/Firmware/CHANGELOG.md @@ -9,13 +9,13 @@ Please add a note of your changes below this heading if you make a Pull Request. ### Changed * The DFU script now verifies the flash after writing * Refactor python tools - * The scripts `explore_odrive.py`, `liveplotter.py`, `drv_status.py` and `rate_test.py` have been merged into one single `odrvtool` script. Running this script without any arguments provides the shell that `explore_odrive.py` used to provide. - * The command line options of `odrvtool` have changed compared to the original `explore_odrive.py`. See `odrvtool --help` for more details. - * `odrvtool` (previously `explore_odrive.py`) now supports controlling multiple ODrives concurrently (`odrv0`, `odrv1`, ...) - * No need to restart the `odrvtool` shell when devices get disconnected and reconnected + * The scripts `explore_odrive.py`, `liveplotter.py`, `drv_status.py` and `rate_test.py` have been merged into one single `odrivetool` script. Running this script without any arguments provides the shell that `explore_odrive.py` used to provide. + * The command line options of `odrivetool` have changed compared to the original `explore_odrive.py`. See `odrivetool --help` for more details. + * `odrivetool` (previously `explore_odrive.py`) now supports controlling multiple ODrives concurrently (`odrv0`, `odrv1`, ...) + * No need to restart the `odrivetool` shell when devices get disconnected and reconnected * ODrive accesses from within python tools are now thread-safe. That means you can read from the same remote property from multiple threads concurrently. - * The liveplotter (`odrvtool liveplotter`, formerly `liveplotter.py`) does no longer steal focus and closes as expected - * (experimental: start liveplotter from `odrvtool` shell by typing `start_liveplotter(lambda: odrv0.motor0.encoder.encoder_state)`) + * The liveplotter (`odrivetool liveplotter`, formerly `liveplotter.py`) does no longer steal focus and closes as expected + * (experimental: start liveplotter from `odrivetool` shell by typing `start_liveplotter(lambda: odrv0.motor0.encoder.encoder_state)`) ### Fixed diff --git a/Firmware/Makefile b/Firmware/Makefile index 73e1d8db..d587105a 100644 --- a/Firmware/Makefile +++ b/Firmware/Makefile @@ -16,7 +16,7 @@ gdb: all arm-none-eabi-gdb $(FIRMWARE) -x openocd.gdbinit dfu: all - ../tools/odrvtool $(if $(value SERIAL_NUMBER),--serial-number $(SERIAL_NUMBER),) dfu $(FIRMWARE_HEX) + ../tools/odrivetool $(if $(value SERIAL_NUMBER),--serial-number $(SERIAL_NUMBER),) dfu $(FIRMWARE_HEX) bmp: all arm-none-eabi-gdb --ex 'target extended-remote /dev/stlink' \ diff --git a/Firmware/README.md b/Firmware/README.md index cd4a677d..5ce7736e 100644 --- a/Firmware/README.md +++ b/Firmware/README.md @@ -139,7 +139,7 @@ For working with the ODrive code you don't need an IDE, but the open-source IDE ## Communicating over USB or UART Warning: If testing USB or UART communication for the first time it is recommend that your motors are free to spin continuously and are not connected to a drivetrain with limited travel. ### From Linux/Windows/macOS -There are two example python scripts to help you get started with controlling the ODrive using python. One will drop you into an interactive shell to query settings, parameters, and variables, and let you send setpoints manually ([tools/odrvtool](../tools/odrvtool)). The other is a demo application to show you how to control the ODrive programmatically ([tools/odrive_demo.py](../tools/odrive_demo.py)). Below follows a step-by-step guide on how to run these. +There are two example python scripts to help you get started with controlling the ODrive using python. One will drop you into an interactive shell to query settings, parameters, and variables, and let you send setpoints manually ([tools/odrivetool](../tools/odrivetool)). The other is a demo application to show you how to control the ODrive programmatically ([tools/odrive_demo.py](../tools/odrive_demo.py)). Below follows a step-by-step guide on how to run these. * __Windows__: It is recommended to use a Unix style command prompt, such as Git Bash that comes with [Git for windows](https://git-scm.com/download/win). @@ -165,9 +165,9 @@ pip install pyusb pyserial 5. __Windows__: Use the [Zadig](http://zadig.akeo.ie/) utility to set ODrive (not STLink!) driver to libusb-win32. * If 'Odrive version 3.x' is not in the list of devices upon opening Zadig, check 'List All Devices' from the options menu. With the Odrive selected in the device list choose 'libusb-win32' from the target driver list and select the large 'install driver' button. 6. Open the bash prompt in the `ODrive/tools/` folder. -7. Run `python3 odrive_demo.py` or `python3 odrvtool`. +7. Run `python3 odrive_demo.py` or `python3 odrivetool`. - `odrive_demo.py` is a very simple script which will make motor 0 turn back and forth. Use this as an example if you want to control the ODrive yourself programatically. -- `odrvtool` drops you into an interactive python shell when started without any arguments. There you can explore and edit the parameters that are available on your device. For instance `odrv0.motor0.pos_setpoint = 10000` makes motor0 move to position 10000. To connect over serial instead of USB run `./tools/odrvtool --path serial`. Run `./tools/odrvtool --help` to see what else you can do with the script. +- `odrivetool` drops you into an interactive python shell when started without any arguments. There you can explore and edit the parameters that are available on your device. For instance `odrv0.motor0.pos_setpoint = 10000` makes motor0 move to position 10000. To connect over serial instead of USB run `./tools/odrivetool --path serial`. Run `./tools/odrivetool --help` to see what else you can do with the script. ### From Arduino [See ODrive Arduino Library](https://github.com/madcowswe/ODriveArduino) @@ -180,7 +180,7 @@ See the [protocol specification](protocol.md) or the [legacy protocol specificat The majority of the important parameters you would want to set after flashing the ODrive with firmware are configurable over the USB communication interface. These include some mandatory parameters that you must set for correct operation, as well as tuning and optional parameters. To start the configuration session: -* Launch `./tools/odrvtool`. This will give you a command prompt where you can modify using simple assignments. +* Launch `./tools/odrivetool`. This will give you a command prompt where you can modify using simple assignments. * Configure parameters of the `odrv0.[...].config` objects. * For example to adjust the position gain: `odrv0.motor0.config.pos_gain = 30` Enter. * The complete list of configurable parameters is: @@ -253,7 +253,7 @@ If you have an encoder with an index (Z) signal, you may avoid having to do the * All the parameters we will be modifying are in the motor structs at the top of [MotorControl/low_level.c](MotorControl/low_level.c). * Set `.encoder.use_index = true` and `.encoder.calibrated = false`. * Flash this configuration, and let the motor scan for the index pulse and then complete the encoder calibration. -* Run `odrvtool`, check [Communicating over USB or UART](#communicating-over-usb-or-uart) for instructions on how to do that. +* Run `odrivetool`, check [Communicating over USB or UART](#communicating-over-usb-or-uart) for instructions on how to do that. * Enter the following to print out the calibration parameters (substitute the motor number you are calibrating for ``): * `odrv0.motor.encoder.encoder_offset` - This should print a number, like -326 or 1364. * `odrv0.motor.encoder.motor_dir` - This should print 1 or -1. @@ -267,7 +267,7 @@ If you have an encoder with an index (Z) signal, you may avoid having to do the

## Checking for error codes -`odrvtool` can also be used to check error codes when your odrive is not working as expected. For example `odrv0.motor0.error` will list the error code associated with motor 0. +`odrivetool` can also be used to check error codes when your odrive is not working as expected. For example `odrv0.motor0.error` will list the error code associated with motor 0.

The error nummber corresponds to the following: diff --git a/tools/odrvtool b/tools/odrvtool index ae6f2662..3e8211d7 100755 --- a/tools/odrvtool +++ b/tools/odrvtool @@ -14,7 +14,7 @@ print = functools.partial(print, flush=True) ## Parse arguments ## parser = argparse.ArgumentParser(description='ODrive command line utility\n' - 'Running this tool without any arguments is equivalent to running `odrvtool shell`\n', + 'Running this tool without any arguments is equivalent to running `odrivetool shell`\n', formatter_class=argparse.RawTextHelpFormatter) # Subcommands diff --git a/tools/setup.py b/tools/setup.py index 27b4fdcc..b503d710 100644 --- a/tools/setup.py +++ b/tools/setup.py @@ -62,7 +62,7 @@ if creating_package: setup( name = 'odrive', packages = ['odrive'], # this must be the same as the name above - scripts = ['odrvtool', 'odrive_demo.py'], + scripts = ['odrivetool', 'odrive_demo.py'], version = version, description = 'Control utilities for the ODrive high performance motor controller', author = 'Oskar Weigl', From ab698a9dcef8007b1ff06a5aa3deb8e6ac7ec60f Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 5 Apr 2018 22:27:20 -0700 Subject: [PATCH 033/215] turn error enums into flags --- Firmware/MotorControl/axis.cpp | 25 +++++++------ Firmware/MotorControl/axis.hpp | 37 ++++++++++--------- Firmware/MotorControl/encoder.cpp | 6 +-- Firmware/MotorControl/encoder.hpp | 10 +++-- Firmware/MotorControl/low_level.cpp | 12 ++++-- Firmware/MotorControl/low_level.h | 2 +- Firmware/MotorControl/motor.cpp | 14 +++---- Firmware/MotorControl/motor.hpp | 19 ++++++---- Firmware/MotorControl/odrive_main.hpp | 11 ++++++ .../MotorControl/sensorless_estimator.cpp | 2 +- .../MotorControl/sensorless_estimator.hpp | 6 ++- 11 files changed, 85 insertions(+), 59 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index f9696284..fe3760aa 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -101,9 +101,9 @@ bool Axis::check_PSU_brownout() { // Sets error and returns false otherwise. bool Axis::do_checks() { if (!motor_.do_checks()) - return error_ = ERROR_MOTOR_FAILED, false; + return error_ |= ERROR_MOTOR_FAILED, false; if (!check_PSU_brownout()) - return error_ = ERROR_DC_BUS_UNDER_VOLTAGE, false; + return error_ |= ERROR_DC_BUS_UNDER_VOLTAGE, false; return true; } @@ -115,7 +115,7 @@ bool Axis::run_sensorless_spin_up() { float I_mag = config_.spin_up_current * x; x += current_meas_period / config_.ramp_up_time; if (!motor_.update(I_mag, phase)) - return error_ = ERROR_MOTOR_FAILED, false; + return error_ |= ERROR_MOTOR_FAILED, false; return x < 1.0f; }); if (error_ != ERROR_NO_ERROR) @@ -129,7 +129,7 @@ bool Axis::run_sensorless_spin_up() { phase = wrap_pm_pi(phase + vel * current_meas_period); float I_mag = config_.spin_up_current; if (!motor_.update(I_mag, phase)) - return error_ = ERROR_MOTOR_FAILED, false; + return error_ |= ERROR_MOTOR_FAILED, false; return vel < config_.spin_up_target_vel; }); return error_ == ERROR_NO_ERROR; @@ -142,16 +142,16 @@ bool Axis::run_sensorless_control_loop() { float pos_estimate, vel_estimate, phase, current_setpoint; if (controller_.config_.control_mode >= CTRL_MODE_POSITION_CONTROL) - return error_ = ERROR_POS_CTRL_DURING_SENSORLESS, false; + return error_ |= ERROR_POS_CTRL_DURING_SENSORLESS, false; // We update the encoder just in case someone needs the output for testing encoder_.update(nullptr, nullptr, nullptr); if (!sensorless_estimator_.update(&pos_estimate, &vel_estimate, &phase)) - return error_ = ERROR_SENSORLESS_ESTIMATOR_FAILED, false; + return error_ |= ERROR_SENSORLESS_ESTIMATOR_FAILED, false; if (!controller_.update(pos_estimate, vel_estimate, ¤t_setpoint)) - return error_ = ERROR_CONTROLLER_FAILED, false; + return error_ |= ERROR_CONTROLLER_FAILED, false; if (!motor_.update(current_setpoint, phase)) - return error_ = ERROR_MOTOR_FAILED, false; + return error_ |= ERROR_MOTOR_FAILED, false; return true; }); set_step_dir_enabled(false); @@ -166,11 +166,11 @@ bool Axis::run_closed_loop_control_loop() { // We update the sensorless estimator just in case someone needs the output for testing sensorless_estimator_.update(nullptr, nullptr, nullptr); if (!encoder_.update(&pos_estimate, &vel_estimate, &phase)) - return error_ = ERROR_ENCODER_FAILED, false; + return error_ |= ERROR_ENCODER_FAILED, false; if (!controller_.update(pos_estimate, vel_estimate, ¤t_setpoint)) - return error_ = ERROR_CONTROLLER_FAILED, false; + return error_ |= ERROR_CONTROLLER_FAILED, false; if (!motor_.update(current_setpoint, phase)) - return error_ = ERROR_MOTOR_FAILED, false; + return error_ |= ERROR_MOTOR_FAILED, false; return true; }); set_step_dir_enabled(false); @@ -180,6 +180,7 @@ bool Axis::run_closed_loop_control_loop() { bool Axis::run_idle_loop() { // run_control_loop ignores missed modulation timing updates // if and only if we're in AXIS_STATE_IDLE + safety_critical_disarm_motor_pwm(motor_); run_control_loop([this](){ sensorless_estimator_.update(nullptr, nullptr, nullptr); encoder_.update(nullptr, nullptr, nullptr); @@ -276,7 +277,7 @@ void Axis::run_state_machine_loop() { break; default: - error_ = ERROR_INVALID_STATE; + error_ |= ERROR_INVALID_STATE; status = false; // this will set the state to idle break; } diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index c7f909e0..fe5ff15d 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -43,17 +43,17 @@ struct AxisConfig_t { class Axis { public: enum Error_t { - ERROR_NO_ERROR = 0, - ERROR_INVALID_STATE = 1, // void run_control_loop(const T& update_handler) { while (requested_state_ == AXIS_STATE_UNDEFINED) { - if (motor_.error_ != Motor::ERROR_NO_ERROR) { - error_ = ERROR_MOTOR_FAILED; - break; - } if ((current_state_ != AXIS_STATE_IDLE) && (motor_.armed_state_ == Motor::ARMED_STATE_DISARMED)) { // motor got disarmed in something other than the idle loop - error_ = ERROR_CONTROL_LOOP_TIMEOUT; + error_ |= ERROR_MOTOR_DISARMED; + break; + } + if (motor_.error_ != Motor::ERROR_NO_ERROR) { + error_ |= ERROR_MOTOR_FAILED; break; } @@ -127,7 +127,7 @@ public: // safe and float the phases safety_critical_disarm_motor_pwm(motor_); update_brake_current(); - error_ = ERROR_CURRENT_MEASUREMENT_TIMEOUT; + error_ |= ERROR_CURRENT_MEASUREMENT_TIMEOUT; break; } } @@ -190,4 +190,7 @@ public: } }; + +DEFINE_ENUM_FLAG_OPERATORS(Axis::Error_t) + #endif /* __AXIS_HPP */ diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 8fae0d95..19a39792 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -150,7 +150,7 @@ bool Encoder::run_offset_calibration() { float actual_encoder_delta_abs = fabsf((int16_t)hw_config_.timer->Instance->CNT-init_enc_val); if(fabsf(actual_encoder_delta_abs - expected_encoder_delta)/expected_encoder_delta > config_.calib_range) { - error_ = ERROR_CPR_OUT_OF_RANGE; + error_ |= ERROR_CPR_OUT_OF_RANGE; return false; } // check direction @@ -162,7 +162,7 @@ bool Encoder::run_offset_calibration() { axis_->motor_.config_.direction = -1; } else { // Encoder response error - error_ = ERROR_RESPONSE; + error_ |= ERROR_RESPONSE; return false; } @@ -192,7 +192,7 @@ bool Encoder::run_offset_calibration() { bool Encoder::update(float* pos_estimate, float* vel_estimate, float* phase_output) { // Check that we don't get problems with discrete time approximation if (!(current_meas_period * pll_kp_ < 1.0f)) { - error_ = ERROR_NUMERICAL; + error_ |= ERROR_NUMERICAL; return false; } diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 2edf5be0..ae4b3c0e 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -22,10 +22,10 @@ struct EncoderConfig_t { class Encoder { public: enum Error_t { - ERROR_NONE, - ERROR_NUMERICAL, - ERROR_CPR_OUT_OF_RANGE, - ERROR_RESPONSE, + ERROR_NONE = 0, + ERROR_NUMERICAL = 0x01, + ERROR_CPR_OUT_OF_RANGE = 0x02, + ERROR_RESPONSE = 0x04, }; Encoder(const EncoderHardwareConfig_t& hw_config, @@ -83,4 +83,6 @@ public: } }; +DEFINE_ENUM_FLAG_OPERATORS(Encoder::Error_t) + #endif // __ENCODER_HPP diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 01f8a392..98ac5620 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -110,11 +110,14 @@ void safety_critical_arm_motor_pwm(Motor& motor) { // After calling this function, it is guaranteed that all three // motor phases are floating and will not be enabled again until // safety_critical_arm_motor_phases is called. -void safety_critical_disarm_motor_pwm(Motor& motor) { +// @returns true if the motor was in a state other than disarmed before +bool safety_critical_disarm_motor_pwm(Motor& motor) { uint8_t sr = cpu_enter_critical(); + bool was_armed = motor.armed_state_ != Motor::ARMED_STATE_DISARMED; motor.armed_state_ = Motor::ARMED_STATE_DISARMED; __HAL_TIM_MOE_DISABLE_UNCONDITIONALLY(motor.hw_config_.timer); cpu_exit_critical(sr); + return was_armed; } // @brief Updates the phase timings unless the motor is disarmed. @@ -303,7 +306,7 @@ void low_level_fault(Motor::Error_t error) { // Disable all motors NOW! for (size_t i = 0; i < AXIS_COUNT; ++i) { safety_critical_disarm_motor_pwm(axes[i]->motor_); - axes[i]->motor_.error_ = error; + axes[i]->motor_.error_ |= error; } safety_critical_disarm_brake_resistor(); @@ -355,7 +358,10 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { if (!other_axis.motor_.next_timings_valid_) { // the motor control loop failed to update the timings in time // we must assume that it died and therefore float all phases - safety_critical_disarm_motor_pwm(other_axis.motor_); + bool was_armed = safety_critical_disarm_motor_pwm(other_axis.motor_); + if (was_armed) { + other_axis.motor_.error_ |= Motor::ERROR_CONTROL_DEADLINE_MISSED; + } } else { other_axis.motor_.next_timings_valid_ = false; safety_critical_apply_motor_pwm_timings( diff --git a/Firmware/MotorControl/low_level.h b/Firmware/MotorControl/low_level.h index 5f4a7cde..2bd9fe04 100644 --- a/Firmware/MotorControl/low_level.h +++ b/Firmware/MotorControl/low_level.h @@ -22,7 +22,7 @@ extern "C" { /* Exported functions --------------------------------------------------------*/ void safety_critical_arm_motor_pwm(Motor& motor); -void safety_critical_disarm_motor_pwm(Motor& motor); +bool safety_critical_disarm_motor_pwm(Motor& motor); void safety_critical_apply_motor_pwm_timings(Motor& motor, uint16_t timings[3]); void safety_critical_arm_brake_resistor(); void safety_critical_disarm_brake_resistor(); diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index cf0e80f9..0229e697 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -39,7 +39,7 @@ bool Motor::arm() { // that we have exactly one full interrupt period until the third trigger. This gives // the control loop the correct time quota to set up modulation timings. if (!(axis_->wait_for_current_meas() && axis_->wait_for_current_meas())) - return axis_->error_ = Axis::ERROR_CURRENT_MEASUREMENT_TIMEOUT, false; + return axis_->error_ |= Axis::ERROR_CURRENT_MEASUREMENT_TIMEOUT, false; next_timings_valid_ = false; safety_critical_arm_motor_pwm(*this); return true; @@ -119,7 +119,7 @@ bool Motor::check_DRV_fault() { bool Motor::do_checks() { if (!check_DRV_fault()) { - error_ = ERROR_DRV_FAULT; + error_ |= ERROR_DRV_FAULT; return false; } return true; @@ -162,7 +162,7 @@ bool Motor::measure_phase_resistance(float test_current, float max_voltage) { float Ialpha = -(current_meas_.phB + current_meas_.phC); test_voltage += (kI * current_meas_period) * (test_current - Ialpha); if (test_voltage > max_voltage || test_voltage < -max_voltage) - return error_ = ERROR_PHASE_RESISTANCE_OUT_OF_RANGE, false; + return error_ |= ERROR_PHASE_RESISTANCE_OUT_OF_RANGE, false; // Test voltage along phase A if (!enqueue_voltage_timings(test_voltage, 0.0f)) @@ -216,14 +216,12 @@ bool Motor::measure_phase_inductance(float voltage_low, float voltage_high) { config_.phase_inductance = L; // TODO arbitrary values set for now if (L < 1e-6f || L > 500e-6f) - return error_ = ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE, false; + return error_ |= ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE, false; return true; } bool Motor::run_calibration() { - error_ = ERROR_NO_ERROR; - float R_calib_max_voltage = config_.resistance_calib_max_voltage; if (config_.motor_type == MOTOR_TYPE_HIGH_CURRENT) { if (!measure_phase_resistance(config_.calibration_current, R_calib_max_voltage)) @@ -245,7 +243,7 @@ bool Motor::run_calibration() { bool Motor::enqueue_modulation_timings(float mod_alpha, float mod_beta) { float tA, tB, tC; if (SVM(mod_alpha, mod_beta, &tA, &tB, &tC) != 0) - return error_ = ERROR_NUMERICAL, false; + return error_ |= ERROR_NUMERICAL, false; next_timings_[0] = (uint16_t)(tA * (float)TIM_1_8_PERIOD_CLOCKS); next_timings_[1] = (uint16_t)(tB * (float)TIM_1_8_PERIOD_CLOCKS); next_timings_[2] = (uint16_t)(tC * (float)TIM_1_8_PERIOD_CLOCKS); @@ -353,7 +351,7 @@ bool Motor::update(float current_setpoint, float phase) { if(!FOC_voltage(0.0f, current_setpoint, phase)) return false; } else { - error_ = ERROR_NOT_IMPLEMENTED_MOTOR_TYPE; + error_ |= ERROR_NOT_IMPLEMENTED_MOTOR_TYPE; return false; } return true; diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 503b8756..b2a79f88 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -54,14 +54,15 @@ typedef struct { class Motor { public: enum Error_t { - ERROR_NO_ERROR, - ERROR_PHASE_RESISTANCE_OUT_OF_RANGE, - ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE, - ERROR_ADC_FAILED, - ERROR_DRV_FAULT, - ERROR_NOT_IMPLEMENTED_MOTOR_TYPE, - ERROR_BRAKE_CURRENT_OUT_OF_RANGE, - ERROR_NUMERICAL + ERROR_NO_ERROR = 0, + ERROR_PHASE_RESISTANCE_OUT_OF_RANGE = 0x01, + ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE = 0x02, + ERROR_ADC_FAILED = 0x04, + ERROR_DRV_FAULT = 0x08, + ERROR_CONTROL_DEADLINE_MISSED = 0x10, + ERROR_NOT_IMPLEMENTED_MOTOR_TYPE = 0x20, + ERROR_BRAKE_CURRENT_OUT_OF_RANGE = 0x40, + ERROR_NUMERICAL = 0x80 }; enum TimingLog_t { @@ -209,4 +210,6 @@ public: } }; +DEFINE_ENUM_FLAG_OPERATORS(Motor::Error_t) + #endif // __MOTOR_HPP diff --git a/Firmware/MotorControl/odrive_main.hpp b/Firmware/MotorControl/odrive_main.hpp index 50bf69c8..1b4b8850 100644 --- a/Firmware/MotorControl/odrive_main.hpp +++ b/Firmware/MotorControl/odrive_main.hpp @@ -41,6 +41,17 @@ extern BoardConfig_t board_config; constexpr size_t AXIS_COUNT = 2; extern Axis *axes[AXIS_COUNT]; +// TODO: move +// this is technically not thread-safe but practically it might be +#define DEFINE_ENUM_FLAG_OPERATORS(ENUMTYPE) \ +inline ENUMTYPE operator | (ENUMTYPE a, ENUMTYPE b) { return static_cast(static_cast>(a) | static_cast>(b)); } \ +inline ENUMTYPE operator & (ENUMTYPE a, ENUMTYPE b) { return static_cast(static_cast>(a) & static_cast>(b)); } \ +inline ENUMTYPE operator ^ (ENUMTYPE a, ENUMTYPE b) { return static_cast(static_cast>(a) ^ static_cast>(b)); } \ +inline ENUMTYPE &operator |= (ENUMTYPE &a, ENUMTYPE b) { return reinterpret_cast(reinterpret_cast&>(a) |= static_cast>(b)); } \ +inline ENUMTYPE &operator &= (ENUMTYPE &a, ENUMTYPE b) { return reinterpret_cast(reinterpret_cast&>(a) &= static_cast>(b)); } \ +inline ENUMTYPE &operator ^= (ENUMTYPE &a, ENUMTYPE b) { return reinterpret_cast(reinterpret_cast&>(a) ^= static_cast>(b)); } \ +inline ENUMTYPE operator ~ (ENUMTYPE a) { return static_cast(~static_cast>(a)); } + // ODrive specific includes #include diff --git a/Firmware/MotorControl/sensorless_estimator.cpp b/Firmware/MotorControl/sensorless_estimator.cpp index 0fa3c7fe..4ae081f4 100644 --- a/Firmware/MotorControl/sensorless_estimator.cpp +++ b/Firmware/MotorControl/sensorless_estimator.cpp @@ -24,7 +24,7 @@ bool SensorlessEstimator::update(float* pos_estimate, float* vel_estimate, float // Check that we don't get problems with discrete time approximation if (!(current_meas_period * pll_kp_ < 1.0f)) { - error_ = ERROR_NUMERICAL; + error_ |= ERROR_NUMERICAL; return false; } diff --git a/Firmware/MotorControl/sensorless_estimator.hpp b/Firmware/MotorControl/sensorless_estimator.hpp index 740ae8c1..910bc05a 100644 --- a/Firmware/MotorControl/sensorless_estimator.hpp +++ b/Firmware/MotorControl/sensorless_estimator.hpp @@ -4,8 +4,8 @@ class SensorlessEstimator { public: enum Error_t { - ERROR_NONE, - ERROR_NUMERICAL, + ERROR_NONE = 0, + ERROR_NUMERICAL = 0x01, }; SensorlessEstimator(); @@ -40,4 +40,6 @@ public: } }; +DEFINE_ENUM_FLAG_OPERATORS(SensorlessEstimator::Error_t) + #endif /* __SENSORLESS_ESTIMATOR_HPP */ From 6719a2dfe054ff2607cd447bbfada9d72dd8c4c6 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 6 Apr 2018 12:52:56 -0700 Subject: [PATCH 034/215] add PROGRAMMER=... parameter to makefile --- Firmware/Makefile | 18 +++++++++++++----- Firmware/find_programmer.sh | 2 ++ 2 files changed, 15 insertions(+), 5 deletions(-) create mode 100755 Firmware/find_programmer.sh diff --git a/Firmware/Makefile b/Firmware/Makefile index 899f043c..921a9e13 100644 --- a/Firmware/Makefile +++ b/Firmware/Makefile @@ -5,12 +5,21 @@ BUILD_DIR = build FIRMWARE = $(BUILD_DIR)/ODriveFirmware.elf FIRMWARE_HEX = $(BUILD_DIR)/ODriveFirmware.hex +PROGRAMMER_HEX := $(shell echo $(PROGRAMMER) | sed -e 's/.\{2\}/\\x&/g') +OPENOCD := openocd -f interface/stlink-v2.cfg \ + $(if $(value PROGRAMMER),-c 'hla_serial $(PROGRAMMER_HEX)',) \ + -f target/stm32f4x.cfg + all: @tup --quiet --no-environ-check flash: all - openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg -c init -c reset\ halt -c flash\ write_image\ erase\ $(FIRMWARE) -c reset\ run -c exit + $(OPENOCD) -c init \ + -c 'reset halt' \ + -c 'flash write_image erase $(FIRMWARE)' \ + -c 'reset run' \ + -c exit gdb: all arm-none-eabi-gdb $(FIRMWARE) -x openocd.gdbinit @@ -26,11 +35,11 @@ bmp: all # Erase entire STM32 erase: - openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg -c init -c reset\ halt -c flash\ erase_address\ 0x8000000\ 0x100000 -c reset\ run -c exit + $(OPENOCD) -c init -c reset\ halt -c flash\ erase_address\ 0x8000000\ 0x100000 -c reset\ run -c exit # Erase all configuration from the ODrive erase_config: - openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg -c init -c reset\ halt -c flash\ erase_address\ 0x80C0000\ 0x40000 -c reset\ init -c reset\ run -c exit + $(OPENOCD) -c init -c reset\ halt -c flash\ erase_address\ 0x80C0000\ 0x40000 -c reset\ init -c reset\ run -c exit # The one-time programmable memory stores the board version # has the following format: @@ -51,8 +60,7 @@ erase_config: # [write OTP] write_otp: ifeq ($(ODRV_FACTORY),TRUE) - # Data: - openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg \ + $(OPENOCD) \ -c init \ -c 'reset halt' \ -c 'mww 0x40023C04 0x45670123' \ diff --git a/Firmware/find_programmer.sh b/Firmware/find_programmer.sh new file mode 100755 index 00000000..97a94928 --- /dev/null +++ b/Firmware/find_programmer.sh @@ -0,0 +1,2 @@ +#!/bin/bash +openocd -d3 -f board/stm32f4discovery.cfg -c "hla_serial wrong_serial" 2>&1 | xxd -p | tr -d '\n' | sed -n 's/^.*6e756d6265722027\([0-9a-f]*\)2720646f65736e27.*$/\1/p'; echo From 0b4ecb0581054cbc5a17c5964c25be73937bcd70 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 6 Apr 2018 12:59:14 -0700 Subject: [PATCH 035/215] add motor argument to odrive.utils.print_drv_regs --- tools/odrive/utils.py | 20 ++++++++++---------- tools/odrvtool | 3 ++- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 79c69b01..2acb9430 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -72,21 +72,21 @@ def start_liveplotter(get_var_callback): threading.Thread(target=plot_data).start() #plot_data() -def print_drv_regs(device): +def print_drv_regs(name, motor): """ - Dumps the current gate driver regisers for Motor 0 + Dumps the current gate driver regisers for the specified motor """ - fault = device.motor0.gate_driver.drv_fault - status_reg_1 = device.motor0.gate_driver.status_reg_1 - status_reg_2 = device.motor0.gate_driver.status_reg_2 - ctrl_reg_1 = device.motor0.gate_driver.ctrl_reg_1 - ctrl_reg_2 = device.motor0.gate_driver.ctrl_reg_2 - + fault = motor.gate_driver.drv_fault + status_reg_1 = motor.gate_driver.status_reg_1 + status_reg_2 = motor.gate_driver.status_reg_2 + ctrl_reg_1 = motor.gate_driver.ctrl_reg_1 + ctrl_reg_2 = motor.gate_driver.ctrl_reg_2 + print(name + ": " + str(fault)) print("DRV Fault Code: " + str(fault)) print("Status Reg 1: " + str(status_reg_1) + " (" + format(status_reg_1, '#010b') + ")") print("Status Reg 2: " + str(status_reg_2) + " (" + format(status_reg_2, '#010b') + ")") - print("Control Reg 1: " + str(ctrl_reg_1) + " (" + format(ctrl_reg_1, '#010b') + ")") - print("Control Reg 2: " + str(ctrl_reg_2) + " (" + format(ctrl_reg_2, '#010b') + ")") + print("Control Reg 1: " + str(ctrl_reg_1) + " (" + format(ctrl_reg_1, '#013b') + ")") + print("Control Reg 2: " + str(ctrl_reg_2) + " (" + format(ctrl_reg_2, '#09b') + ")") def rate_test(device): """ diff --git a/tools/odrvtool b/tools/odrvtool index 3e8211d7..0bcc39b7 100755 --- a/tools/odrvtool +++ b/tools/odrvtool @@ -111,7 +111,8 @@ try: from odrive.utils import print_drv_regs print("Waiting for ODrive...") my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number) - print_drv_regs(my_odrive) + print_drv_regs("Motor 0", my_odrive.axis0.motor) + print_drv_regs("Motor 1", my_odrive.axis1.motor) elif args.command == 'rate-test': from odrive.utils import rate_test From 1aba0cb51318942fe5007eb0e24085422bb942cb Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 6 Apr 2018 13:00:02 -0700 Subject: [PATCH 036/215] add Axis::ERROR_BRAKE_RESISTOR_DISARMED --- Firmware/MotorControl/axis.hpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index fe5ff15d..f6415422 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -48,12 +48,13 @@ public: ERROR_DC_BUS_UNDER_VOLTAGE = 0x02, ERROR_DC_BUS_OVER_VOLTAGE = 0x04, ERROR_CURRENT_MEASUREMENT_TIMEOUT = 0x08, - ERROR_MOTOR_DISARMED = 0x10, // void run_control_loop(const T& update_handler) { while (requested_state_ == AXIS_STATE_UNDEFINED) { + if (!brake_resistor_armed_) { + error_ |= ERROR_BRAKE_RESISTOR_DISARMED; + break; + } if ((current_state_ != AXIS_STATE_IDLE) && (motor_.armed_state_ == Motor::ARMED_STATE_DISARMED)) { // motor got disarmed in something other than the idle loop error_ |= ERROR_MOTOR_DISARMED; From 359f0c656a15c28344e432e019e1dac6dd026e3d Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 6 Apr 2018 13:03:35 -0700 Subject: [PATCH 037/215] make tests work on back-to-back rig, check test preconditions --- tools/odrive/enums.py | 18 +-- tools/odrive/tests.py | 326 +++++++++++++++++++++++++++++++----------- tools/run_tests.py | 121 ++++++++++------ tools/test-rig.yaml | 47 ++++-- 4 files changed, 374 insertions(+), 138 deletions(-) diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index da762bbb..52b1e610 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -13,15 +13,15 @@ AXIS_STATE_CLOSED_LOOP_CONTROL = 8 AXIS_ERROR_NO_ERROR = 0 AXIS_ERROR_INVALID_STATE = 1 -AXIS_ERROR_DC_BUS_UNDER_VOLTAGE = 2 -AXIS_ERROR_DC_BUS_OVER_VOLTAGE = 3 -AXIS_ERROR_CURRENT_MEASUREMENT_TIMEOUT = 4 -AXIS_ERROR_CONTROL_LOOP_TIMEOUT = 5 -AXIS_ERROR_MOTOR_FAILED = 6 -AXIS_ERROR_SENSORLESS_ESTIMATOR_FAILED = 7 -AXIS_ERROR_ENCODER_FAILED = 8 -AXIS_ERROR_CONTROLLER_FAILED = 9 -AXIS_ERROR_POS_CTRL_DURING_SENSORLESS = 10 +#AXIS_ERROR_DC_BUS_UNDER_VOLTAGE = 2 +#AXIS_ERROR_DC_BUS_OVER_VOLTAGE = 3 +#AXIS_ERROR_CURRENT_MEASUREMENT_TIMEOUT = 4 +#AXIS_ERROR_CONTROL_LOOP_TIMEOUT = 5 +#AXIS_ERROR_MOTOR_FAILED = 6 +#AXIS_ERROR_SENSORLESS_ESTIMATOR_FAILED = 7 +#AXIS_ERROR_ENCODER_FAILED = 8 +#AXIS_ERROR_CONTROLLER_FAILED = 9 +#AXIS_ERROR_POS_CTRL_DURING_SENSORLESS = 10 MOTOR_TYPE_HIGH_CURRENT = 0 #MOTOR_TYPE_LOW_CURRENT = 1 diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index 4e9b4398..0cdc121f 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -4,8 +4,10 @@ import shlex import math import time import sys +import threading import odrive.discovery from odrive.enums import * +import odrive.utils import abc ABC = abc.ABC @@ -14,6 +16,36 @@ class TestFailed(Exception): def __init__(self, message): Exception.__init__(self, message) +class PreconditionsNotMet(Exception): + pass + +class ODriveTestContext(): + def __init__(self, name: str, yaml: dict): + self.handle = None + self.yaml = yaml + self.name = name + self.axes = [] + for axis_idx, axis_yaml in enumerate(yaml['axes']): + axis_name = axis_yaml['name'] if 'name' in axis_yaml else '{}.axis{}'.format(name, axis_idx) + self.axes.append(AxisTestContext(axis_name, axis_yaml, self)) + + def rediscover(self): + """ + Reconnects to the ODrive + """ + self.handle = odrive.discovery.find_any( + path="usb", serial_number=self.yaml['serial-number'], timeout=15) + for axis_idx, axis_ctx in enumerate(self.axes): + axis_ctx.handle = self.handle.__dict__['axis{}'.format(axis_idx)] + +class AxisTestContext(): + def __init__(self, name: str, yaml: dict, odrv_ctx: ODriveTestContext): + self.handle = None + self.yaml = yaml + self.name = name + self.lock = threading.Lock() + self.odrv_ctx = odrv_ctx + def test_assert_eq(observed, expected, range=None, accuracy=None): if range is None and accuracy is None and observed != expected: raise TestFailed("value mismatch: expected {} but observed {}".format(expected, observed)) @@ -22,6 +54,19 @@ def test_assert_eq(observed, expected, range=None, accuracy=None): elif not accuracy is None and ((observed < expected * (1 - accuracy)) or (observed > expected * (1 + accuracy))): raise TestFailed("value out of range: expected {}+-{}% but observed {}".format(expected, accuracy*100.0, observed)) +def test_assert_no_error(axis_ctx: AxisTestContext): + errors = [] + if axis_ctx.handle.motor.error != 0: + errors.append("motor failed with error {:04X}".format(axis_ctx.handle.motor.error)) + if axis_ctx.handle.encoder.error != 0: + errors.append("encoder failed with error {:04X}".format(axis_ctx.handle.encoder.error)) + if axis_ctx.handle.sensorless_estimator.error != 0: + errors.append("sensorless_estimator failed with error {:04X}".format(axis_ctx.handle.sensorless_estimator.error)) + if axis_ctx.handle.error != 0: + errors.append("axis failed with error {:04X}".format(axis_ctx.handle.error)) + if len(errors) > 0: + raise TestFailed("\n".join(errors)) + def run(command_line, logger, timeout=None): """ Runs a shell command in the current directory @@ -35,24 +80,51 @@ def run(command_line, logger, timeout=None): logger.error(result.stdout.decode(sys.stdout.encoding)) raise TestFailed("command {} failed".format(command_line)) -def rediscover(odrv_yaml): +def request_state(axis_ctx: AxisTestContext, state, expect_success=True): + axis_ctx.handle.requested_state = state + time.sleep(0.001) + if expect_success: + test_assert_eq(axis_ctx.handle.current_state, state) + else: + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) + test_assert_eq(axis_ctx.handle.error, AXIS_ERROR_INVALID_STATE) + axis_ctx.handle.error = AXIS_ERROR_NO_ERROR # reset error + +def set_limits(axis_ctx: AxisTestContext, logger, vel_limit=20000, current_limit=10): """ - Connects to the ODrive indicated by odrv_yaml + Sets the velocity and current limits for the axis, subject to the following constraints: + - the arguments given to this function are not exceeded + - max motor current is not exceeded + - max brake resistor power divided by two is not exceeded (here velocity takes precedence over current) """ - odrv = odrive.discovery.find_any(path="usb", serial_number=odrv_yaml['serial-number'], timeout=10) - odrv_yaml['odrv'] = odrv - for axis_idx, axis_yaml in enumerate(odrv_yaml['axes']): - axis_yaml['axis'] = odrv.__dict__['axis{}'.format(axis_idx)] - return odrv + max_rpm = vel_limit / axis_ctx.yaml['encoder-cpr'] * 60 + max_emf_voltage = max_rpm / axis_ctx.yaml['motor-kv'] + max_brake_power = axis_ctx.odrv_ctx.yaml['max-brake-power'] / 2 * 0.8 # 20% safety margin + max_motor_current = max_brake_power / max_emf_voltage + logger.debug("velocity limit = {} => V_emf = {:.3}V, I_lim = {:.3}A".format(vel_limit, max_emf_voltage, max_motor_current)) + + # Bound current limit based on the motor's current limit and the brake resistor current limit + current_limit = min(current_limit, axis_ctx.yaml['motor-max-current'], max_motor_current) + # TODO: set as an atomic operation + axis_ctx.handle.motor.config.current_lim = current_limit + axis_ctx.handle.controller.config.vel_limit = vel_limit + class ODriveTest(ABC): """ Tests inheriting from this class get full ownership of the ODrive being tested. However no guarantees are made for the mechanical state of the axes. + The test can demand exclusive run time which means that the host will + not run any other test at the same time. This can be used if the test + invokes a command that's so lame that it can't run twice concurrently. """ + def __init__(self, exclusive=False): + self._exclusive = exclusive + def check_preconditions(self, odrv_ctx: ODriveTestContext, logger): + pass @abc.abstractmethod - def run_test(self, odrv, odrv_config, logger): + def run_test(self, odrv_ctx: ODriveTestContext, logger): pass class AxisTest(ABC): @@ -62,8 +134,16 @@ class AxisTest(ABC): axis, the other axis is guaranteed to be disabled (high impedance) during this test. """ + def check_preconditions(self, axis_ctx: AxisTestContext, logger): + test_assert_no_error(axis_ctx) + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) + if (abs(axis_ctx.handle.encoder.pll_vel) > 500): + logger.warn("axis still in motion, delaying 2 sec...") + time.sleep(2) + test_assert_eq(axis_ctx.handle.encoder.pll_vel, 0, range=500) + @abc.abstractmethod - def run_test(self, axis, axis_config, logger): + def run_test(self, axis_ctx: AxisTestContext, logger): pass class DualAxisTest(ABC): @@ -71,30 +151,48 @@ class DualAxisTest(ABC): Tests using this scope get ownership of two axes that are mechanically coupled. """ + def check_preconditions(self, axis0_ctx: AxisTestContext, axis1_ctx: AxisTestContext, logger): + test_assert_no_error(axis0_ctx) + test_assert_no_error(axis1_ctx) + test_assert_eq(axis0_ctx.handle.current_state, AXIS_STATE_IDLE) + test_assert_eq(axis1_ctx.handle.current_state, AXIS_STATE_IDLE) + test_assert_eq(axis0_ctx.handle.encoder.pll_vel, 0, range=1000) + test_assert_eq(axis1_ctx.handle.encoder.pll_vel, 0, range=1000) + @abc.abstractmethod - def run_test(self, axis0, axis0_config, axis1, axis1_config, logger): + def run_test(self, axis0_ctx: AxisTestContext, axis1_ctx: AxisTestContext, logger): pass +class TestDiscoverAndGotoIdle(ODriveTest): + def run_test(self, odrv_ctx: ODriveTestContext, logger): + odrv_ctx.rediscover() + odrv_ctx.axes[0].handle.error = 0 + odrv_ctx.axes[1].handle.error = 0 + request_state(odrv_ctx.axes[0], AXIS_STATE_IDLE) + request_state(odrv_ctx.axes[1], AXIS_STATE_IDLE) + class TestFlashAndErase(ODriveTest): - def run_test(self, odrv, odrv_config, logger): - run("make flash PROGRAMMER='" + odrv_config['programmer'] + "'", logger, timeout=20) + def __init__(self): + ODriveTest.__init__(self, exclusive=True) + def run_test(self, odrv_ctx: ODriveTestContext, logger): + run("make flash PROGRAMMER='" + odrv_ctx.yaml['programmer'] + "'", logger, timeout=20) # FIXME: device does not reboot correctly after erasing config this way #run("make erase_config PROGRAMMER='" + test_rig.programmer + "'", timeout=10) logger.debug("waiting for ODrive...") - odrv = rediscover(odrv_config) + odrv_ctx.rediscover() # ensure the correct odrive is returned - test_assert_eq(format(odrv.serial_number, 'x').upper(), odrv_config['serial-number']) + test_assert_eq(format(odrv_ctx.handle.serial_number, 'x').upper(), odrv_ctx.yaml['serial-number']) # erase configuration and reboot logger.debug("erasing old configuration...") - odrv.erase_configuration() + odrv_ctx.handle.erase_configuration() #time.sleep(0.1) try: # FIXME: sometimes the device does not reappear after this ("no response - probably incompatible") # this is a firmware issue since it persists when unplugging/replugging # but goes away when power cycling the device - odrv.reboot() + odrv_ctx.handle.reboot() except odrive.protocol.ChannelBrokenException: pass # this is expected time.sleep(0.5) @@ -103,36 +201,26 @@ class TestSetup(ODriveTest): """ Preconditions: ODrive is unconfigured and just rebooted """ - def run_test(self, odrv, odrv_config, logger): - odrv = rediscover(odrv_config) + def run_test(self, odrv_ctx: ODriveTestContext, logger): + odrv_ctx.rediscover() # initial protocol tests and setup logger.debug("setting up ODrive...") - odrv.config.enable_uart = True - test_assert_eq(odrv.config.enable_uart, True) - odrv.config.enable_uart = False - test_assert_eq(odrv.config.enable_uart, False) - odrv.config.brake_resistance = 1.0 - test_assert_eq(odrv.config.brake_resistance, 1.0) - odrv.config.brake_resistance = odrv_config['brake-resistance'] - test_assert_eq(odrv.config.brake_resistance, odrv_config['brake-resistance'], accuracy=0.01) + odrv_ctx.handle.config.enable_uart = True + test_assert_eq(odrv_ctx.handle.config.enable_uart, True) + odrv_ctx.handle.config.enable_uart = False + test_assert_eq(odrv_ctx.handle.config.enable_uart, False) + odrv_ctx.handle.config.brake_resistance = 1.0 + test_assert_eq(odrv_ctx.handle.config.brake_resistance, 1.0) + odrv_ctx.handle.config.brake_resistance = odrv_ctx.yaml['brake-resistance'] + test_assert_eq(odrv_ctx.handle.config.brake_resistance, odrv_ctx.yaml['brake-resistance'], accuracy=0.01) # firmware has 1500ms startup delay time.sleep(2) logger.debug("ensure we're in idle state") - test_assert_eq(odrv.axis0.current_state, AXIS_STATE_IDLE) - test_assert_eq(odrv.axis1.current_state, AXIS_STATE_IDLE) - -def request_state(axis, state, expect_success=True): - axis.requested_state = state - time.sleep(0.001) - if expect_success: - test_assert_eq(axis.current_state, state) - else: - test_assert_eq(axis.current_state, AXIS_STATE_IDLE) - test_assert_eq(axis.error, AXIS_ERROR_INVALID_STATE) - axis.error = AXIS_ERROR_NO_ERROR # reset error + test_assert_eq(odrv_ctx.handle.axis0.current_state, AXIS_STATE_IDLE) + test_assert_eq(odrv_ctx.handle.axis1.current_state, AXIS_STATE_IDLE) class TestMotorCalibration(AxisTest): """ @@ -141,25 +229,29 @@ class TestMotorCalibration(AxisTest): Preconditions: The motor must be uncalibrated. Postconditions: The motor will be calibrated after this test. """ - def run_test(self, axis, axis_config, logger): + def check_preconditions(self, axis_ctx: AxisTestContext, logger): + super(TestMotorCalibration, self).check_preconditions(axis_ctx, logger) + test_assert_eq(axis_ctx.handle.motor.is_calibrated, False) + + def run_test(self, axis_ctx: AxisTestContext, logger): logger.debug("try to enter closed loop control (should be rejected)") - request_state(axis, AXIS_STATE_CLOSED_LOOP_CONTROL, expect_success=False) + request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL, expect_success=False) logger.debug("try to start encoder index search (should be rejected)") - request_state(axis, AXIS_STATE_ENCODER_INDEX_SEARCH, expect_success=False) + request_state(axis_ctx, AXIS_STATE_ENCODER_INDEX_SEARCH, expect_success=False) logger.debug("try to start encoder offset calibration (should be rejected)") - request_state(axis, AXIS_STATE_ENCODER_OFFSET_CALIBRATION, expect_success=False) + request_state(axis_ctx, AXIS_STATE_ENCODER_OFFSET_CALIBRATION, expect_success=False) logger.debug("motor calibration (takes about 4.5 seconds)") - axis.motor.config.pole_pairs = axis_config['motor-pole-pairs'] - request_state(axis, AXIS_STATE_MOTOR_CALIBRATION) + axis_ctx.handle.motor.config.pole_pairs = axis_ctx.yaml['motor-pole-pairs'] + request_state(axis_ctx, AXIS_STATE_MOTOR_CALIBRATION) time.sleep(6) - test_assert_eq(axis.current_state, AXIS_STATE_IDLE) - test_assert_eq(axis.error, AXIS_ERROR_NO_ERROR) - test_assert_eq(axis.motor.config.phase_resistance, axis_config['motor-phase-resistance'], accuracy=0.1) - test_assert_eq(axis.motor.config.phase_inductance, axis_config['motor-phase-inductance'], accuracy=0.5) - axis.motor.config.pre_calibrated = True + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) + test_assert_eq(axis_ctx.handle.error, AXIS_ERROR_NO_ERROR) + test_assert_eq(axis_ctx.handle.motor.config.phase_resistance, axis_ctx.yaml['motor-phase-resistance'], accuracy=0.2) + test_assert_eq(axis_ctx.handle.motor.config.phase_inductance, axis_ctx.yaml['motor-phase-inductance'], accuracy=0.5) + axis_ctx.handle.motor.config.pre_calibrated = True class TestEncoderOffsetCalibration(AxisTest): """ @@ -167,19 +259,32 @@ class TestEncoderOffsetCalibration(AxisTest): Preconditions: The encoder must be non-ready. Postconditions: The encoder will be ready after this test. """ - def run_test(self, axis, axis_config, logger): + def __init__(self, pass_if_ready=False): + AxisTest.__init__(self) + self._pass_if_ready = pass_if_ready + + def check_preconditions(self, axis_ctx: AxisTestContext, logger): + super(TestEncoderOffsetCalibration, self).check_preconditions(axis_ctx, logger) + if not self._pass_if_ready: + test_assert_eq(axis_ctx.handle.encoder.is_ready, False) + + def run_test(self, axis_ctx: AxisTestContext, logger): + if (self._pass_if_ready and axis_ctx.handle.encoder.is_ready): + logger.debug("encoder already ready, skipping this test") + return + logger.debug("try to enter closed loop control (should be rejected)") - request_state(axis, AXIS_STATE_CLOSED_LOOP_CONTROL, expect_success=False) + request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL, expect_success=False) logger.debug("encoder offset calibration (takes about 9.5 seconds)") - axis.encoder.config.cpr = axis_config['encoder-cpr'] # TODO: test setting a wrong CPR - request_state(axis, AXIS_STATE_ENCODER_OFFSET_CALIBRATION) + axis_ctx.handle.encoder.config.cpr = axis_ctx.yaml['encoder-cpr'] # TODO: test setting a wrong CPR + request_state(axis_ctx, AXIS_STATE_ENCODER_OFFSET_CALIBRATION) # TODO: ensure the encoder calibration doesn't do crap time.sleep(11) - test_assert_eq(axis.current_state, AXIS_STATE_IDLE) - test_assert_eq(axis.error, AXIS_ERROR_NO_ERROR) - test_assert_eq(axis.motor.config.direction, axis_config['motor-direction']) - axis.encoder.config.pre_calibrated = True + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) + test_assert_eq(axis_ctx.handle.error, AXIS_ERROR_NO_ERROR) + test_assert_eq(axis_ctx.handle.motor.config.direction, axis_ctx.yaml['motor-direction']) + axis_ctx.handle.encoder.config.pre_calibrated = True class TestClosedLoopControl(AxisTest): """ @@ -187,49 +292,108 @@ class TestClosedLoopControl(AxisTest): and verifies that the sensorless estimator works Precondition: The axis is calibrated and ready for closed loop control """ - def run_test(self, axis, axis_config, logger): + def check_preconditions(self, axis_ctx: AxisTestContext, logger): + super(TestClosedLoopControl, self).check_preconditions(axis_ctx, logger) + test_assert_eq(axis_ctx.handle.motor.is_calibrated, True) + test_assert_eq(axis_ctx.handle.encoder.is_ready, True) + + def run_test(self, axis_ctx: AxisTestContext, logger): logger.debug("closed loop control: test tiny position changes") - axis.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL + axis_ctx.handle.requested_state = AXIS_STATE_CLOSED_LOOP_CONTROL time.sleep(0.001) - test_assert_eq(axis.current_state, AXIS_STATE_CLOSED_LOOP_CONTROL) + test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_CLOSED_LOOP_CONTROL) time.sleep(0.1) # give the PLL some time to settle - test_assert_eq(axis.encoder.pll_pos, 0, range=300) - axis.controller.set_pos_setpoint(1000, 0, 0) + init_pos = axis_ctx.handle.encoder.pll_pos + axis_ctx.handle.controller.set_pos_setpoint(init_pos+1000, 0, 0) time.sleep(0.5) - test_assert_eq(axis.encoder.pll_pos, 1000, range=200) - axis.controller.set_pos_setpoint(-1000, 0, 0) + test_assert_eq(axis_ctx.handle.encoder.pll_pos, init_pos+1000, range=200) + axis_ctx.handle.controller.set_pos_setpoint(init_pos-1000, 0, 0) time.sleep(0.5) - test_assert_eq(axis.encoder.pll_pos, -1000, range=200) + test_assert_eq(axis_ctx.handle.encoder.pll_pos, init_pos-1000, range=400) logger.debug("closed loop control: test vel_limit") - axis.controller.set_pos_setpoint(50000, 0, 0) - axis.controller.config.vel_limit = 40000 + axis_ctx.handle.controller.set_pos_setpoint(50000, 0, 0) + axis_ctx.handle.controller.config.vel_limit = 40000 time.sleep(0.3) - test_assert_eq(axis.encoder.pll_vel, 40000, range=4000) - expected_sensorless_estimation = 40000 * 2 * math.pi / axis_config['encoder-cpr'] * axis_config['motor-pole-pairs'] - test_assert_eq(axis.sensorless_estimator.pll_vel, expected_sensorless_estimation, range=50) + test_assert_eq(axis_ctx.handle.encoder.pll_vel, 40000, range=4000) + expected_sensorless_estimation = 40000 * 2 * math.pi / axis_ctx.yaml['encoder-cpr'] * axis_ctx.yaml['motor-pole-pairs'] + test_assert_eq(axis_ctx.handle.sensorless_estimator.pll_vel, expected_sensorless_estimation, range=50) time.sleep(3) - test_assert_eq(axis.encoder.pll_vel, 0, range=1000) + test_assert_eq(axis_ctx.handle.encoder.pll_vel, 0, range=1000) + time.sleep(0.5) + request_state(axis_ctx, AXIS_STATE_IDLE) class TestStoreAndReboot(ODriveTest): """ Stores the current configuration to NVM and reboots. """ - def run_test(self, odrv, odrv_config, logger): + def run_test(self, odrv_ctx: ODriveTestContext, logger): logger.debug("storing configuration and rebooting...") - odrv.save_configuration() + odrv_ctx.handle.save_configuration() try: - odrv.reboot() + odrv_ctx.handle.reboot() except odrive.protocol.ChannelBrokenException: pass # this is expected time.sleep(2) - odrv = rediscover(odrv_config) + odrv_ctx.rediscover() logger.debug("verifying configuration after reboot...") - test_assert_eq(odrv.config.brake_resistance, odrv_config['brake-resistance'], accuracy=0.01) - for axis_config in odrv_config['axes']: - axis = axis_config['axis'] - test_assert_eq(axis.encoder.config.cpr, axis_config['encoder-cpr']) - test_assert_eq(axis.motor.config.phase_resistance, axis_config['motor-phase-resistance'], accuracy=0.1) - test_assert_eq(axis.motor.config.phase_inductance, axis_config['motor-phase-inductance'], accuracy=0.5) + test_assert_eq(odrv_ctx.handle.config.brake_resistance, odrv_ctx.yaml['brake-resistance'], accuracy=0.01) + for axis_ctx in odrv_ctx.axes: + test_assert_eq(axis_ctx.handle.encoder.config.cpr, axis_ctx.yaml['encoder-cpr']) + test_assert_eq(axis_ctx.handle.motor.config.phase_resistance, axis_ctx.yaml['motor-phase-resistance'], accuracy=0.15) + test_assert_eq(axis_ctx.handle.motor.config.phase_inductance, axis_ctx.yaml['motor-phase-inductance'], accuracy=0.5) + +class TestVelCtrlVsPosCtrl(DualAxisTest): + """ + Uses one ODrive as a load operating in velocity control mode. + The other ODrive tries to "fight" against the load in position mode. + """ + def run_test(self, axis0_ctx: AxisTestContext, axis1_ctx: AxisTestContext, logger): + load_ctx = axis0_ctx + driver_ctx = axis1_ctx + + # Set up viscous fluid load + logger.debug("activating load on {}...".format(load_ctx.name)) + load_ctx.handle.controller.config.vel_integrator_gain = 0 + load_ctx.handle.controller.vel_integrator_current = 0 + set_limits(load_ctx, logger, vel_limit=100000, current_limit=50) + load_ctx.handle.controller.set_vel_setpoint(0, 0) + request_state(load_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) + + # Turn to some position + logger.debug("using {} as driver against load, vel=100000...".format(driver_ctx.name)) + set_limits(driver_ctx, logger, vel_limit=100000, current_limit=50) + init_pos = driver_ctx.handle.encoder.pll_pos + driver_ctx.handle.controller.set_pos_setpoint(init_pos + 100000, 0, 0) + request_state(driver_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) + for _ in range(int(4000/5)): + logger.debug(str(driver_ctx.handle.motor.current_control.Iq_setpoint)) + time.sleep(0.005) + + test_assert_no_error(load_ctx) + test_assert_no_error(driver_ctx) + + logger.debug("using {} as driver against load, vel=20000...".format(driver_ctx.name)) + set_limits(driver_ctx, logger, vel_limit=20000, current_limit=50) + init_pos = driver_ctx.handle.encoder.pll_pos + driver_ctx.handle.controller.set_pos_setpoint(init_pos + 100000, 0, 0) + request_state(driver_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) + #for _ in range(int(5*4000/5)): + # logger.debug(str(driver_ctx.handle.motor.current_control.Iq_setpoint)) + # time.sleep(0.005) + time.sleep(7) + + odrive.utils.print_drv_regs("load motor ({})".format(load_ctx.name), load_ctx.handle.motor) + odrive.utils.print_drv_regs("driver motor ({})".format(driver_ctx.name), driver_ctx.handle.motor) + + test_assert_no_error(load_ctx) + test_assert_no_error(driver_ctx) + + ## Turn to another position + #logger.debug("controlling against load, vel=40000...") + #set_limits(axis1_ctx, logger, vel_limit=40000, current_limit=20) + #init_pos = axis1_ctx.handle.encoder.pll_pos + #axis1_ctx.handle.controller.set_pos_setpoint(init_pos + 100000, 0, 0) + #request_state(axis1_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) diff --git a/tools/run_tests.py b/tools/run_tests.py index 463d6939..968000e6 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -17,15 +17,19 @@ from odrive.utils import Logger, for_all_parallel all_tests = [ - TestFlashAndErase(), - TestSetup(), - TestMotorCalibration(), - # TODO: test encoder index search - TestEncoderOffsetCalibration(), - TestClosedLoopControl(), - TestStoreAndReboot(), - TestEncoderOffsetCalibration(), # need to find offset _or_ index after reboot - TestClosedLoopControl() +# TestFlashAndErase(), +# TestSetup(), +# TestMotorCalibration(), +# # TODO: test encoder index search +# TestEncoderOffsetCalibration(), +# # TODO: hold down one motor while the other one does an index search (should fail) +# TestClosedLoopControl(), +# TestStoreAndReboot(), +# TestEncoderOffsetCalibration(), # need to find offset _or_ index after reboot +# TestClosedLoopControl(), + TestDiscoverAndGotoIdle(), # for testing + TestEncoderOffsetCalibration(pass_if_ready=True), + TestVelCtrlVsPosCtrl() # TODO: test step/dir # TODO: test sensorless # TODO: test ASCII protocol @@ -41,65 +45,101 @@ with open(script_path + '/test-rig.yaml', 'r') as file_stream: os.chdir(script_path + '/../Firmware') -# Ensure every device has a name -for idx, odrv_yaml in enumerate(test_rig_yaml['odrives']): - if not 'name' in odrv_yaml: - odrv_yaml['name'] = 'odrive{}'.format(idx) +# Build a dictionary of odrive test contexts by name +odrives_by_name = {} +for odrv_idx, odrv_yaml in enumerate(test_rig_yaml['odrives']): + name = odrv_yaml['name'] if 'name' in odrv_yaml else 'odrive{}'.format(odrv_idx) + odrives_by_name[name] = ODriveTestContext(name, odrv_yaml) -# Build a dictionary of axes by name (e.g. odrive0.axis0) -# Also ensure every axis has a name and mutex +# Build a dictionary of axis test contexts by name (e.g. odrive0.axis0) axes_by_name = {} -for odrv_yaml in test_rig_yaml['odrives']: - for axis_idx, axis_yaml in enumerate(odrv_yaml['axes']): - if not 'name' in axis_yaml: - axis_yaml['name'] = '{}.axis{}'.format(odrv_yaml['name'], axis_idx) - axis_yaml['lock'] = threading.Lock() - axes_by_name[axis_yaml['name']] = axis_yaml +for odrv_ctx in odrives_by_name.values(): + for axis_idx, axis_ctx in enumerate(odrv_ctx.axes): + axes_by_name[axis_ctx.name] = axis_ctx # Ensure mechanical couplings are valid +couplings = [] if test_rig_yaml['couplings'] is None: test_rig_yaml['couplings'] = {} else: - for axis in sum(test_rig_yaml['couplings'], []): - if not axis in axes_by_name: - logger.error('Unknown axis {} in list of mechanical couplings'.format(axis)) + for coupling in test_rig_yaml['couplings']: + couplings.append([axes_by_name[axis_name] for axis_name in coupling]) try: for test in all_tests: if isinstance(test, ODriveTest): - def odrv_test_thread(odrv_yaml): - test_subject_name = odrv_yaml['name'] - logger.info('● running {} on {}...'.format(type(test).__name__, test_subject_name)) - odrv = odrv_yaml['odrv'] if 'odrv' in odrv_yaml else None - test.run_test(odrv, odrv_yaml, - logger.indent(' {}: '.format(test_subject_name))) + def odrv_test_thread(odrv_name): + odrv_ctx = odrives_by_name[odrv_name] + logger.info('● running {} on {}...'.format(type(test).__name__, odrv_name)) + try: + test.check_preconditions(odrv_ctx, + logger.indent(' {}: '.format(odrv_name))) + except: + raise PreconditionsNotMet() + test.run_test(odrv_ctx, + logger.indent(' {}: '.format(odrv_name))) - for_all_parallel(test_rig_yaml['odrives'], lambda x: x['name'], odrv_test_thread) + if test._exclusive: + for odrv in odrives_by_name: + odrv_test_thread(odrv) + else: + for_all_parallel(odrives_by_name, lambda x: x, odrv_test_thread) elif isinstance(test, AxisTest): def axis_test_thread(axis_name): # Get all axes that are mechanically coupled with the axis specified by axis_name - conflicting_axes = sum([c for c in test_rig_yaml['couplings'] if (axis_name in c)], []) + conflicting_axes = sum([c for c in couplings if (axis_name in [a.name for a in c])], []) # Remove duplicates conflicting_axes = list(set(conflicting_axes)) # Acquire lock for all conflicting axes - conflicting_axes.sort() # prevent deadlocks + conflicting_axes.sort(key=lambda x: x.name) # prevent deadlocks + axis_ctx = axes_by_name[axis_name] for conflicting_axis in conflicting_axes: - axes_by_name[conflicting_axis]['lock'].acquire() + conflicting_axis.lock.acquire() try: # Run test on this axis logger.info('● running {} on {}...'.format(type(test).__name__, axis_name)) - axis_yaml = axes_by_name[axis_name] - test.run_test(axis_yaml['axis'], axis_yaml, + try: + test.check_preconditions(axis_ctx, + logger.indent(' {}: '.format(axis_name))) + except: + raise PreconditionsNotMet() + test.run_test(axis_ctx, logger.indent(' {}: '.format(axis_name))) finally: # Release all conflicting axes for conflicting_axis in conflicting_axes: - axes_by_name[conflicting_axis]['lock'].release() + conflicting_axis.lock.release() for_all_parallel(axes_by_name, lambda x: x, axis_test_thread) + elif isinstance(test, DualAxisTest): + def dual_axis_test_thread(coupling): + coupling_name = "...".join([a.name for a in coupling]) + # Remove duplicates + coupled_axes = list(set(coupling)) + # Acquire lock for all conflicting axes + coupled_axes.sort(key=lambda x: x.name) # prevent deadlocks + for axis_ctx in coupled_axes: + axis_ctx.lock.acquire() + try: + # Run test on this axis + logger.info('● running {} on {}...'.format(type(test).__name__, coupling_name)) + try: + test.check_preconditions(coupled_axes[0], coupled_axes[1], + logger.indent(' {}: '.format(coupling_name))) + except: + raise PreconditionsNotMet() + test.run_test(coupled_axes[0], coupled_axes[1], + logger.indent(' {}: '.format(coupling_name))) + finally: + # Release all conflicting axes + for axis_ctx in coupled_axes: + axis_ctx.lock.release() + + for_all_parallel(couplings, lambda x: "..".join([a.name for a in x]), dual_axis_test_thread) + else: logger.warn("ignoring unknown test type {}".format(type(test))) @@ -109,9 +149,10 @@ except: try: dont_secure_after_failure = True # TODO: disable if not dont_secure_after_failure: - def odrv_reset_thread(odrv_yaml): - run("make erase PROGRAMMER='" + odrv_yaml['programmer'] + "'", logger, timeout=30) - for_all_parallel(test_rig_yaml['odrives'], lambda x: x['name'], odrv_reset_thread) + def odrv_reset_thread(odrv_name): + odrv_ctx = odrives_by_name[odrv_name] + run("make erase PROGRAMMER='" + odrv_ctx.yaml['programmer'] + "'", logger, timeout=30) + for_all_parallel(odrives_by_name, lambda x: x['name'], odrv_reset_thread) except: logger.error('///////////////////////////////////////////') logger.error('/// CRITICAL: COULD NOT SECURE TEST RIG ///') diff --git a/tools/test-rig.yaml b/tools/test-rig.yaml index 875898b3..05e26696 100644 --- a/tools/test-rig.yaml +++ b/tools/test-rig.yaml @@ -1,25 +1,56 @@ # ODrives odrives: - - board-version: v3.4-24V + - name: top-odrive + board-version: v3.4-24V serial-number: "385F324D3037" brake-resistance: 0.47 - uart: /dev/serial/by-id/... + uart: /dev/serial/by-id/[not-yet-used] usb: auto - programmer: /dev... + programmer: '533f7506493f49514454193f' + vbus-voltage: 12 # [V] + max-brake-power: 150 # [W] axes: - - motor-phase-resistance: 0.033 - motor-phase-inductance: 1.6e-05 + - motor-phase-resistance: 0.0245 + motor-phase-inductance: 2.03e-05 motor-pole-pairs: 7 - motor-direction: 1 + motor-direction: -1 + motor-kv: 190 + motor-max-current: 50 encoder-cpr: 8192 - motor-phase-resistance: 0.028 motor-phase-inductance: 1.6e-05 motor-pole-pairs: 7 motor-direction: -1 + motor-kv: 270 + motor-max-current: 50 + encoder-cpr: 8192 + - name: bottom-odrive + board-version: v3.4-48V + serial-number: "306A396A3235" + brake-resistance: 0.47 + uart: /dev/serial/by-id/[not-yet-used] + usb: auto + programmer: '493f6f06493f56540929113f' + vbus-voltage: 12 # [V] + max-brake-power: 150 # [W] + axes: + - motor-phase-resistance: 0.0253 + motor-phase-inductance: 1.6e-05 + motor-pole-pairs: 7 + motor-direction: 1 + motor-kv: 270 + motor-max-current: 50 + encoder-cpr: 8192 + - motor-phase-resistance: 0.0245 + motor-phase-inductance: 2.03e-05 + motor-pole-pairs: 7 + motor-direction: -1 + motor-kv: 190 + motor-max-current: 50 encoder-cpr: 8192 # Mechanical couplings couplings: - #- [ odrive0.axis0, odrive1.axis0 ] - #- [ odrive0.axis1, odrive1.axis1 ] + - [ top-odrive.axis0, bottom-odrive.axis1 ] + - [ top-odrive.axis1, bottom-odrive.axis0 ] From 6cc3558656788cb3610543d429bc9dfaeb457a62 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 6 Apr 2018 12:59:14 -0700 Subject: [PATCH 038/215] add motor argument to odrive.utils.print_drv_regs --- tools/odrive/utils.py | 20 ++++++++++---------- tools/odrvtool | 3 ++- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 28e2c830..810780d0 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -72,21 +72,21 @@ def start_liveplotter(get_var_callback): threading.Thread(target=plot_data).start() #plot_data() -def print_drv_regs(device): +def print_drv_regs(name, motor): """ - Dumps the current gate driver regisers for Motor 0 + Dumps the current gate driver regisers for the specified motor """ - fault = device.motor0.gate_driver.drv_fault - status_reg_1 = device.motor0.gate_driver.status_reg_1 - status_reg_2 = device.motor0.gate_driver.status_reg_2 - ctrl_reg_1 = device.motor0.gate_driver.ctrl_reg_1 - ctrl_reg_2 = device.motor0.gate_driver.ctrl_reg_2 - + fault = motor.gate_driver.drv_fault + status_reg_1 = motor.gate_driver.status_reg_1 + status_reg_2 = motor.gate_driver.status_reg_2 + ctrl_reg_1 = motor.gate_driver.ctrl_reg_1 + ctrl_reg_2 = motor.gate_driver.ctrl_reg_2 + print(name + ": " + str(fault)) print("DRV Fault Code: " + str(fault)) print("Status Reg 1: " + str(status_reg_1) + " (" + format(status_reg_1, '#010b') + ")") print("Status Reg 2: " + str(status_reg_2) + " (" + format(status_reg_2, '#010b') + ")") - print("Control Reg 1: " + str(ctrl_reg_1) + " (" + format(ctrl_reg_1, '#010b') + ")") - print("Control Reg 2: " + str(ctrl_reg_2) + " (" + format(ctrl_reg_2, '#010b') + ")") + print("Control Reg 1: " + str(ctrl_reg_1) + " (" + format(ctrl_reg_1, '#013b') + ")") + print("Control Reg 2: " + str(ctrl_reg_2) + " (" + format(ctrl_reg_2, '#09b') + ")") def rate_test(device): """ diff --git a/tools/odrvtool b/tools/odrvtool index 3e8211d7..0bcc39b7 100755 --- a/tools/odrvtool +++ b/tools/odrvtool @@ -111,7 +111,8 @@ try: from odrive.utils import print_drv_regs print("Waiting for ODrive...") my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number) - print_drv_regs(my_odrive) + print_drv_regs("Motor 0", my_odrive.axis0.motor) + print_drv_regs("Motor 1", my_odrive.axis1.motor) elif args.command == 'rate-test': from odrive.utils import rate_test From bd2bcd401b524523a9c1e139f551e0c5a3265337 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 6 Apr 2018 21:05:48 -0700 Subject: [PATCH 039/215] add usb_burn_in_test --- tools/odrive/utils.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 2acb9430..9852a3f2 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -109,6 +109,27 @@ def rate_test(device): FramePerSec = loopsPerSec/loopsPerFrame print("Frames per second: " + str(FramePerSec)) +def usb_burn_in_test(get_var_callback, cancellation_token): + """ + Starts background threads that read a values form the USB device in a spin-loop + """ + + def fetch_data(): + global vals + i = 0 + while not cancellation_token.is_set(): + try: + get_var_callback() + i += 1 + except Exception as ex: + print(str(ex)) + time.sleep(1) + i = 0 + continue + if i % 1000 == 0: + print("read {} values".format(i)) + threading.Thread(target=fetch_data).start() + ## Exceptions ## From f286bd5a3db0924313f0bfd1f5f777ac11ddfe73 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 6 Apr 2018 21:17:13 -0700 Subject: [PATCH 040/215] if USB fails, call set_configuration instead of clear_halt set_configuration acts as a sort of soft reset, clearing halt conditions along the way. More details: http://libusb.sourceforge.net/api-1.0/group__dev.html#ga186593ecae576dad6cd9679f45a2aa43 --- tools/odrive/usbbulk_transport.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/tools/odrive/usbbulk_transport.py b/tools/odrive/usbbulk_transport.py index e1004ba8..fbfcfcee 100644 --- a/tools/odrive/usbbulk_transport.py +++ b/tools/odrive/usbbulk_transport.py @@ -19,6 +19,7 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) def __init__(self, dev, printer): self._printer = printer self.dev = dev + self.intf = None self._name = "USB device {}:{}".format(dev.idVendor, dev.idProduct) self._was_damaged = False @@ -45,18 +46,17 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) if platform.system() != 'Windows': self.dev.reset() + interface_number = 1 try: - if self.dev.is_kernel_driver_active(1): - self.dev.detach_kernel_driver(1) - self._printer("Detached Kernel Driver\n") + if self.dev.is_kernel_driver_active(interface_number): + self.dev.detach_kernel_driver(interface_number) + self._printer("Detached Kernel Driver") except NotImplementedError: pass #is_kernel_driver_active not implemented on Windows - # set the active configuration. With no arguments, the first - # configuration will be the active one - self.dev.set_configuration() - # get an endpoint instance + + self.dev.set_configuration() # no args: set first configuration self.cfg = self.dev.get_active_configuration() - self.intf = self.cfg[(1,0)] + self.intf = self.cfg[(1,0)] # this implicitly claims the interface # write endpoint self.epw = usb.util.find_descriptor(self.intf, # match the first OUT endpoint @@ -66,7 +66,7 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) usb.util.ENDPOINT_OUT ) assert self.epw is not None - self._printer("EndpointAddress for writing {}\n".format(self.epw.bEndpointAddress)) + self._printer("EndpointAddress for writing {}".format(self.epw.bEndpointAddress)) # read endpoint self.epr = usb.util.find_descriptor(self.intf, # match the first IN endpoint @@ -76,10 +76,11 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) usb.util.ENDPOINT_IN ) assert self.epr is not None - self._printer("EndpointAddress for reading {}\n".format(self.epr.bEndpointAddress)) + self._printer("EndpointAddress for reading {}".format(self.epr.bEndpointAddress)) - def shutdown(self): - return 0 + def deinit(self): + if not self.intf is None: + usb.util.release_interface(self.dev, self.intf) def process_packet(self, usbBuffer): try: @@ -97,7 +98,8 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) self._printer("halt condition: {}".format(ex.errno)) # Try resetting halt/stall condition try: - self.epw.clear_halt() + self.deinit() + self.init() except usb.core.USBError: raise odrive.protocol.ChannelBrokenException() # Retry transfer @@ -122,7 +124,8 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) self._printer("halt condition: {}".format(ex.errno)) # Try resetting halt/stall condition try: - self.epr.clear_halt() + self.deinit() + self.init() except usb.core.USBError: raise odrive.protocol.ChannelBrokenException() # Retry transfer From 21f74379de413d434a777b488e8a7eb6eba307f2 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 6 Apr 2018 21:19:24 -0700 Subject: [PATCH 041/215] set usb task pump priority to osPriorityAboveNormal osPriorityNormal is the same priority as the communication task. If the USB pump task runs on the same priority, it sometimes fails to respond to the host in time, causing spurious halt conditions. --- Firmware/MotorControl/communication.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/communication.cpp b/Firmware/MotorControl/communication.cpp index 07515e9a..ce51ded3 100644 --- a/Firmware/MotorControl/communication.cpp +++ b/Firmware/MotorControl/communication.cpp @@ -169,7 +169,7 @@ void init_communication(void) { thread_cmd_parse = osThreadCreate(osThread(task_cmd_parse), NULL); // Start USB interrupt handler thread - osThreadDef(task_usb_pump, usb_update_thread, osPriorityNormal, 0, 512); + osThreadDef(task_usb_pump, usb_update_thread, osPriorityAboveNormal, 0, 512); thread_usb_pump = osThreadCreate(osThread(task_usb_pump), NULL); } From 8b23e0b975ab3890c9a78420cf5785658786b0b1 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 6 Apr 2018 21:17:13 -0700 Subject: [PATCH 042/215] if USB fails, call set_configuration instead of clear_halt set_configuration acts as a sort of soft reset, clearing halt conditions along the way. More details: http://libusb.sourceforge.net/api-1.0/group__dev.html#ga186593ecae576dad6cd9679f45a2aa43 --- tools/odrive/usbbulk_transport.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/tools/odrive/usbbulk_transport.py b/tools/odrive/usbbulk_transport.py index e1004ba8..fbfcfcee 100644 --- a/tools/odrive/usbbulk_transport.py +++ b/tools/odrive/usbbulk_transport.py @@ -19,6 +19,7 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) def __init__(self, dev, printer): self._printer = printer self.dev = dev + self.intf = None self._name = "USB device {}:{}".format(dev.idVendor, dev.idProduct) self._was_damaged = False @@ -45,18 +46,17 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) if platform.system() != 'Windows': self.dev.reset() + interface_number = 1 try: - if self.dev.is_kernel_driver_active(1): - self.dev.detach_kernel_driver(1) - self._printer("Detached Kernel Driver\n") + if self.dev.is_kernel_driver_active(interface_number): + self.dev.detach_kernel_driver(interface_number) + self._printer("Detached Kernel Driver") except NotImplementedError: pass #is_kernel_driver_active not implemented on Windows - # set the active configuration. With no arguments, the first - # configuration will be the active one - self.dev.set_configuration() - # get an endpoint instance + + self.dev.set_configuration() # no args: set first configuration self.cfg = self.dev.get_active_configuration() - self.intf = self.cfg[(1,0)] + self.intf = self.cfg[(1,0)] # this implicitly claims the interface # write endpoint self.epw = usb.util.find_descriptor(self.intf, # match the first OUT endpoint @@ -66,7 +66,7 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) usb.util.ENDPOINT_OUT ) assert self.epw is not None - self._printer("EndpointAddress for writing {}\n".format(self.epw.bEndpointAddress)) + self._printer("EndpointAddress for writing {}".format(self.epw.bEndpointAddress)) # read endpoint self.epr = usb.util.find_descriptor(self.intf, # match the first IN endpoint @@ -76,10 +76,11 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) usb.util.ENDPOINT_IN ) assert self.epr is not None - self._printer("EndpointAddress for reading {}\n".format(self.epr.bEndpointAddress)) + self._printer("EndpointAddress for reading {}".format(self.epr.bEndpointAddress)) - def shutdown(self): - return 0 + def deinit(self): + if not self.intf is None: + usb.util.release_interface(self.dev, self.intf) def process_packet(self, usbBuffer): try: @@ -97,7 +98,8 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) self._printer("halt condition: {}".format(ex.errno)) # Try resetting halt/stall condition try: - self.epw.clear_halt() + self.deinit() + self.init() except usb.core.USBError: raise odrive.protocol.ChannelBrokenException() # Retry transfer @@ -122,7 +124,8 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) self._printer("halt condition: {}".format(ex.errno)) # Try resetting halt/stall condition try: - self.epr.clear_halt() + self.deinit() + self.init() except usb.core.USBError: raise odrive.protocol.ChannelBrokenException() # Retry transfer From d413f3a490805c8aa80658765a2764c1ecc5899a Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sat, 7 Apr 2018 21:49:34 -0700 Subject: [PATCH 043/215] rename odrvtool to odrivetool and include odrive.dfuse in pip package --- tools/{odrvtool => odrivetool} | 0 tools/setup.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename tools/{odrvtool => odrivetool} (100%) diff --git a/tools/odrvtool b/tools/odrivetool similarity index 100% rename from tools/odrvtool rename to tools/odrivetool diff --git a/tools/setup.py b/tools/setup.py index b503d710..1c43b0a1 100644 --- a/tools/setup.py +++ b/tools/setup.py @@ -61,7 +61,7 @@ if creating_package: setup( name = 'odrive', - packages = ['odrive'], # this must be the same as the name above + packages = ['odrive', 'odrive.dfuse'], # this must be the same as the name above scripts = ['odrivetool', 'odrive_demo.py'], version = version, description = 'Control utilities for the ODrive high performance motor controller', From 7c0f0669e670bf932fcf704bbd2578a4e3f9f349 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sat, 7 Apr 2018 22:14:35 -0700 Subject: [PATCH 044/215] fix warning that occurs with some versions of IPython --- tools/odrive/shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/odrive/shell.py b/tools/odrive/shell.py index 21670e3d..af2e9432 100644 --- a/tools/odrive/shell.py +++ b/tools/odrive/shell.py @@ -98,7 +98,7 @@ def launch_shell(args, logger, printer, app_shutdown_token): # If IPython is installed, embed IPython shell, otherwise embed regular shell if use_ipython: help = lambda: print_help(args) # Override help function # pylint: disable=W0612 - console = IPython.terminal.embed.InteractiveShellEmbed(local_ns=interactive_variables, banner1='') + console = IPython.terminal.embed.InteractiveShellEmbed(banner1='') console.runcode = console.run_code # hack to make IPython look like the regular console interact = console else: From 8c2223de2028f5f15f53f3c4cf2999d10b25a8e4 Mon Sep 17 00:00:00 2001 From: Brandon Kinman Date: Sun, 8 Apr 2018 12:13:32 -0700 Subject: [PATCH 045/215] System version checking now works as expected --- tools/odrive/protocol.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/odrive/protocol.py b/tools/odrive/protocol.py index 18f44a41..c7305b19 100644 --- a/tools/odrive/protocol.py +++ b/tools/odrive/protocol.py @@ -6,14 +6,14 @@ import sys import abc -# if sys.version_info >= (3, 4): -ABC = abc.ABC -# else: -# ABC = abc.ABCMeta('ABC', (), {}) +if (sys.version_info[0], sys.version_info[1]) >= (3, 4): + ABC = abc.ABC +else: + ABC = abc.ABCMeta('ABC', (), {}) -# if sys.version_info <= (3, 3): -# from monotonic import monotonic -# time.monotonic = monotonic +if (sys.version_info[0], sys.version_info[1]) <= (3, 3): + from monotonic import monotonic + time.monotonic = monotonic SYNC_BYTE = 0xAA CRC8_INIT = 0x42 From 39ef119515e6315d0f8c771e6c522e71f98a3d71 Mon Sep 17 00:00:00 2001 From: Brandon Kinman Date: Sun, 8 Apr 2018 15:29:49 -0700 Subject: [PATCH 046/215] Making explore_odrive compatible with python27 --- tools/explore_odrive.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/explore_odrive.py b/tools/explore_odrive.py index 9efaebb7..ebe23bf8 100755 --- a/tools/explore_odrive.py +++ b/tools/explore_odrive.py @@ -1,3 +1,5 @@ +from __future__ import print_function + #!/usr/bin/env python3 """ Load an odrive object to play with in the IPython interactive shell. From 68ffd112805200c543a7cfbe93842eb8b301448a Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sun, 8 Apr 2018 20:38:18 -0700 Subject: [PATCH 047/215] change pin names and modes in Cube --- Firmware/Board/v3/Odrive.ioc | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/Firmware/Board/v3/Odrive.ioc b/Firmware/Board/v3/Odrive.ioc index ce8e1e2f..f13657b1 100644 --- a/Firmware/Board/v3/Odrive.ioc +++ b/Firmware/Board/v3/Odrive.ioc @@ -238,7 +238,7 @@ PA13.Signal=SYS_JTMS-SWDIO PA14.Mode=Serial_Wire PA14.Signal=SYS_JTCK-SWCLK PA15.GPIOParameters=GPIO_Label -PA15.GPIO_Label=M0_ENC_Z +PA15.GPIO_Label=GPIO_7 PA15.Locked=true PA15.Signal=GPIO_Input PA2.GPIOParameters=GPIO_PuPd,GPIO_Label @@ -256,7 +256,7 @@ PA4.GPIO_Label=M1_TEMP PA4.Locked=true PA4.Signal=ADCx_IN4 PA5.GPIOParameters=GPIO_Label -PA5.GPIO_Label=AUX_I +PA5.GPIO_Label=AUX_TEMP PA5.Locked=true PA5.Signal=ADCx_IN5 PA6.GPIOParameters=GPIO_Label @@ -314,11 +314,11 @@ PB15.Locked=true PB15.Mode=PWM Generation3 CH3 CH3N PB15.Signal=TIM1_CH3N PB2.GPIOParameters=GPIO_Label -PB2.GPIO_Label=GPIO_5 +PB2.GPIO_Label=GPIO_6 PB2.Locked=true PB2.Signal=GPIO_Input PB3.GPIOParameters=GPIO_Label -PB3.GPIO_Label=M1_ENC_Z +PB3.GPIO_Label=GPIO_8 PB3.Locked=true PB3.Signal=GPIO_Input PB4.GPIOParameters=GPIO_Label @@ -360,9 +360,9 @@ PC14-OSC32_IN.Locked=true PC14-OSC32_IN.PinState=GPIO_PIN_SET PC14-OSC32_IN.Signal=GPIO_Output PC15-OSC32_OUT.GPIOParameters=GPIO_Label -PC15-OSC32_OUT.GPIO_Label=M1_DC_CAL +PC15-OSC32_OUT.GPIO_Label=M1_ENC_Z PC15-OSC32_OUT.Locked=true -PC15-OSC32_OUT.Signal=GPIO_Output +PC15-OSC32_OUT.Signal=GPIO_Input PC2.GPIOParameters=GPIO_Label PC2.GPIO_Label=M1_IC PC2.Signal=ADCx_IN12 @@ -370,8 +370,9 @@ PC3.GPIOParameters=GPIO_Label PC3.GPIO_Label=M1_IB PC3.Signal=ADCx_IN13 PC4.GPIOParameters=GPIO_Label -PC4.GPIO_Label=AUX_TEMP -PC4.Signal=ADCx_IN14 +PC4.GPIO_Label=GPIO_5 +PC4.Locked=true +PC4.Signal=GPIO_Input PC5.GPIOParameters=GPIO_Label PC5.GPIO_Label=M0_TEMP PC5.Signal=ADCx_IN15 @@ -388,9 +389,9 @@ PC8.GPIO_Label=M1_CH PC8.Locked=true PC8.Signal=S_TIM8_CH3 PC9.GPIOParameters=GPIO_Label -PC9.GPIO_Label=M0_DC_CAL +PC9.GPIO_Label=M0_ENC_Z PC9.Locked=true -PC9.Signal=GPIO_Output +PC9.Signal=GPIO_Input PCC.Checker=false PCC.Line=STM32F405/415 PCC.MCU=STM32F405RGTx @@ -484,9 +485,6 @@ SH.ADCx_IN13.0=ADC1_IN13,IN13 SH.ADCx_IN13.1=ADC2_IN13,IN13 SH.ADCx_IN13.2=ADC3_IN13,IN13 SH.ADCx_IN13.ConfNb=3 -SH.ADCx_IN14.0=ADC1_IN14,IN14 -SH.ADCx_IN14.1=ADC2_IN14,IN14 -SH.ADCx_IN14.ConfNb=2 SH.ADCx_IN15.0=ADC1_IN15,IN15 SH.ADCx_IN15.1=ADC2_IN15,IN15 SH.ADCx_IN15.ConfNb=2 From 80470547839f83cfb215d8b8e9e958e09bb7d087 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 9 Apr 2018 17:23:23 -0700 Subject: [PATCH 048/215] generate CubeMX files for board version v3.5 The files main.h, adc.c and gpio.c change from version v3.4 to v3.5 The old versions are moved into the prev_board_version/ directories. --- Firmware/Board/v3/Inc/main.h | 31 +- .../Board/v3/Inc/prev_board_ver/main_V3_2.h | 1 + .../Board/v3/Inc/prev_board_ver/main_V3_4.h | 91 +++++ Firmware/Board/v3/Src/adc.c | 23 +- Firmware/Board/v3/Src/gpio.c | 20 +- .../Board/v3/Src/prev_board_ver/adc_V3_4.c | 375 ++++++++++++++++++ .../Board/v3/Src/prev_board_ver/gpio_V3_4.c | 71 ++++ 7 files changed, 579 insertions(+), 33 deletions(-) create mode 100644 Firmware/Board/v3/Inc/prev_board_ver/main_V3_4.h create mode 100644 Firmware/Board/v3/Src/prev_board_ver/adc_V3_4.c create mode 100644 Firmware/Board/v3/Src/prev_board_ver/gpio_V3_4.c diff --git a/Firmware/Board/v3/Inc/main.h b/Firmware/Board/v3/Inc/main.h index ac9bd857..1ff706df 100644 --- a/Firmware/Board/v3/Inc/main.h +++ b/Firmware/Board/v3/Inc/main.h @@ -58,6 +58,9 @@ #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 1 \ || HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 2 #include "prev_board_ver/main_V3_2.h" +#elif HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 3 \ +|| HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 4 +#include "prev_board_ver/main_V3_4.h" #else /* USER CODE END Includes */ @@ -74,8 +77,8 @@ #define M0_nCS_GPIO_Port GPIOC #define M1_nCS_Pin GPIO_PIN_14 #define M1_nCS_GPIO_Port GPIOC -#define M1_DC_CAL_Pin GPIO_PIN_15 -#define M1_DC_CAL_GPIO_Port GPIOC +#define M1_ENC_Z_Pin GPIO_PIN_15 +#define M1_ENC_Z_GPIO_Port GPIOC #define M0_IB_Pin GPIO_PIN_0 #define M0_IB_GPIO_Port GPIOC #define M0_IC_Pin GPIO_PIN_1 @@ -95,22 +98,22 @@ #define GPIO_4_GPIO_Port GPIOA #define M1_TEMP_Pin GPIO_PIN_4 #define M1_TEMP_GPIO_Port GPIOA -#define AUX_I_Pin GPIO_PIN_5 -#define AUX_I_GPIO_Port GPIOA +#define AUX_TEMP_Pin GPIO_PIN_5 +#define AUX_TEMP_GPIO_Port GPIOA #define VBUS_S_Pin GPIO_PIN_6 #define VBUS_S_GPIO_Port GPIOA #define M1_AL_Pin GPIO_PIN_7 #define M1_AL_GPIO_Port GPIOA -#define AUX_TEMP_Pin GPIO_PIN_4 -#define AUX_TEMP_GPIO_Port GPIOC +#define GPIO_5_Pin GPIO_PIN_4 +#define GPIO_5_GPIO_Port GPIOC #define M0_TEMP_Pin GPIO_PIN_5 #define M0_TEMP_GPIO_Port GPIOC #define M1_BL_Pin GPIO_PIN_0 #define M1_BL_GPIO_Port GPIOB #define M1_CL_Pin GPIO_PIN_1 #define M1_CL_GPIO_Port GPIOB -#define GPIO_5_Pin GPIO_PIN_2 -#define GPIO_5_GPIO_Port GPIOB +#define GPIO_6_Pin GPIO_PIN_2 +#define GPIO_6_GPIO_Port GPIOB #define AUX_L_Pin GPIO_PIN_10 #define AUX_L_GPIO_Port GPIOB #define AUX_H_Pin GPIO_PIN_11 @@ -129,20 +132,20 @@ #define M1_BH_GPIO_Port GPIOC #define M1_CH_Pin GPIO_PIN_8 #define M1_CH_GPIO_Port GPIOC -#define M0_DC_CAL_Pin GPIO_PIN_9 -#define M0_DC_CAL_GPIO_Port GPIOC +#define M0_ENC_Z_Pin GPIO_PIN_9 +#define M0_ENC_Z_GPIO_Port GPIOC #define M0_AH_Pin GPIO_PIN_8 #define M0_AH_GPIO_Port GPIOA #define M0_BH_Pin GPIO_PIN_9 #define M0_BH_GPIO_Port GPIOA #define M0_CH_Pin GPIO_PIN_10 #define M0_CH_GPIO_Port GPIOA -#define M0_ENC_Z_Pin GPIO_PIN_15 -#define M0_ENC_Z_GPIO_Port GPIOA +#define GPIO_7_Pin GPIO_PIN_15 +#define GPIO_7_GPIO_Port GPIOA #define nFAULT_Pin GPIO_PIN_2 #define nFAULT_GPIO_Port GPIOD -#define M1_ENC_Z_Pin GPIO_PIN_3 -#define M1_ENC_Z_GPIO_Port GPIOB +#define GPIO_8_Pin GPIO_PIN_3 +#define GPIO_8_GPIO_Port GPIOB #define M0_ENC_A_Pin GPIO_PIN_4 #define M0_ENC_A_GPIO_Port GPIOB #define M0_ENC_B_Pin GPIO_PIN_5 diff --git a/Firmware/Board/v3/Inc/prev_board_ver/main_V3_2.h b/Firmware/Board/v3/Inc/prev_board_ver/main_V3_2.h index 8c8eff81..bd3f6305 100644 --- a/Firmware/Board/v3/Inc/prev_board_ver/main_V3_2.h +++ b/Firmware/Board/v3/Inc/prev_board_ver/main_V3_2.h @@ -6,6 +6,7 @@ #define TIM_APB1_CLOCK_HZ 84000000 #define TIM_APB1_PERIOD_CLOCKS 4096 #define TIM_APB1_DEADTIME_CLOCKS 40 +#define configAPPLICATION_ALLOCATED_HEAP 1 #define M0_nCS_Pin GPIO_PIN_13 #define M0_nCS_GPIO_Port GPIOC diff --git a/Firmware/Board/v3/Inc/prev_board_ver/main_V3_4.h b/Firmware/Board/v3/Inc/prev_board_ver/main_V3_4.h new file mode 100644 index 00000000..19428406 --- /dev/null +++ b/Firmware/Board/v3/Inc/prev_board_ver/main_V3_4.h @@ -0,0 +1,91 @@ + +/* Private define ------------------------------------------------------------*/ +#define TIM_1_8_CLOCK_HZ 168000000 +#define TIM_1_8_PERIOD_CLOCKS 10192 +#define TIM_1_8_DEADTIME_CLOCKS 20 +#define TIM_APB1_CLOCK_HZ 84000000 +#define TIM_APB1_PERIOD_CLOCKS 4096 +#define TIM_APB1_DEADTIME_CLOCKS 40 +#define configAPPLICATION_ALLOCATED_HEAP 1 + +#define M0_nCS_Pin GPIO_PIN_13 +#define M0_nCS_GPIO_Port GPIOC +#define M1_nCS_Pin GPIO_PIN_14 +#define M1_nCS_GPIO_Port GPIOC +#define M1_DC_CAL_Pin GPIO_PIN_15 +#define M1_DC_CAL_GPIO_Port GPIOC +#define M0_IB_Pin GPIO_PIN_0 +#define M0_IB_GPIO_Port GPIOC +#define M0_IC_Pin GPIO_PIN_1 +#define M0_IC_GPIO_Port GPIOC +#define M1_IC_Pin GPIO_PIN_2 +#define M1_IC_GPIO_Port GPIOC +#define M1_IB_Pin GPIO_PIN_3 +#define M1_IB_GPIO_Port GPIOC +#define GPIO_1_Pin GPIO_PIN_0 +#define GPIO_1_GPIO_Port GPIOA +#define GPIO_2_Pin GPIO_PIN_1 +#define GPIO_2_GPIO_Port GPIOA +#define GPIO_3_Pin GPIO_PIN_2 +#define GPIO_3_GPIO_Port GPIOA +#define GPIO_3_EXTI_IRQn EXTI2_IRQn +#define GPIO_4_Pin GPIO_PIN_3 +#define GPIO_4_GPIO_Port GPIOA +#define M1_TEMP_Pin GPIO_PIN_4 +#define M1_TEMP_GPIO_Port GPIOA +#define AUX_I_Pin GPIO_PIN_5 +#define AUX_I_GPIO_Port GPIOA +#define VBUS_S_Pin GPIO_PIN_6 +#define VBUS_S_GPIO_Port GPIOA +#define M1_AL_Pin GPIO_PIN_7 +#define M1_AL_GPIO_Port GPIOA +#define AUX_TEMP_Pin GPIO_PIN_4 +#define AUX_TEMP_GPIO_Port GPIOC +#define M0_TEMP_Pin GPIO_PIN_5 +#define M0_TEMP_GPIO_Port GPIOC +#define M1_BL_Pin GPIO_PIN_0 +#define M1_BL_GPIO_Port GPIOB +#define M1_CL_Pin GPIO_PIN_1 +#define M1_CL_GPIO_Port GPIOB +#define GPIO_5_Pin GPIO_PIN_2 +#define GPIO_5_GPIO_Port GPIOB +#define AUX_L_Pin GPIO_PIN_10 +#define AUX_L_GPIO_Port GPIOB +#define AUX_H_Pin GPIO_PIN_11 +#define AUX_H_GPIO_Port GPIOB +#define EN_GATE_Pin GPIO_PIN_12 +#define EN_GATE_GPIO_Port GPIOB +#define M0_AL_Pin GPIO_PIN_13 +#define M0_AL_GPIO_Port GPIOB +#define M0_BL_Pin GPIO_PIN_14 +#define M0_BL_GPIO_Port GPIOB +#define M0_CL_Pin GPIO_PIN_15 +#define M0_CL_GPIO_Port GPIOB +#define M1_AH_Pin GPIO_PIN_6 +#define M1_AH_GPIO_Port GPIOC +#define M1_BH_Pin GPIO_PIN_7 +#define M1_BH_GPIO_Port GPIOC +#define M1_CH_Pin GPIO_PIN_8 +#define M1_CH_GPIO_Port GPIOC +#define M0_DC_CAL_Pin GPIO_PIN_9 +#define M0_DC_CAL_GPIO_Port GPIOC +#define M0_AH_Pin GPIO_PIN_8 +#define M0_AH_GPIO_Port GPIOA +#define M0_BH_Pin GPIO_PIN_9 +#define M0_BH_GPIO_Port GPIOA +#define M0_CH_Pin GPIO_PIN_10 +#define M0_CH_GPIO_Port GPIOA +#define M0_ENC_Z_Pin GPIO_PIN_15 +#define M0_ENC_Z_GPIO_Port GPIOA +#define nFAULT_Pin GPIO_PIN_2 +#define nFAULT_GPIO_Port GPIOD +#define M1_ENC_Z_Pin GPIO_PIN_3 +#define M1_ENC_Z_GPIO_Port GPIOB +#define M0_ENC_A_Pin GPIO_PIN_4 +#define M0_ENC_A_GPIO_Port GPIOB +#define M0_ENC_B_Pin GPIO_PIN_5 +#define M0_ENC_B_GPIO_Port GPIOB +#define M1_ENC_A_Pin GPIO_PIN_6 +#define M1_ENC_A_GPIO_Port GPIOB +#define M1_ENC_B_Pin GPIO_PIN_7 +#define M1_ENC_B_GPIO_Port GPIOB diff --git a/Firmware/Board/v3/Src/adc.c b/Firmware/Board/v3/Src/adc.c index 6d0db380..bb536024 100644 --- a/Firmware/Board/v3/Src/adc.c +++ b/Firmware/Board/v3/Src/adc.c @@ -57,6 +57,9 @@ #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 1 \ || HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 2 #include "prev_board_ver/adc_V3_2.c" +#elif HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 3 \ +|| HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 4 +#include "prev_board_ver/adc_V3_4.c" #else /* USER CODE END 0 */ @@ -241,16 +244,15 @@ void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle) PA4 ------> ADC1_IN4 PA5 ------> ADC1_IN5 PA6 ------> ADC1_IN6 - PC4 ------> ADC1_IN14 PC5 ------> ADC1_IN15 */ GPIO_InitStruct.Pin = M0_IB_Pin|M0_IC_Pin|M1_IC_Pin|M1_IB_Pin - |AUX_TEMP_Pin|M0_TEMP_Pin; + |M0_TEMP_Pin; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); - GPIO_InitStruct.Pin = M1_TEMP_Pin|AUX_I_Pin|VBUS_S_Pin; + GPIO_InitStruct.Pin = M1_TEMP_Pin|AUX_TEMP_Pin|VBUS_S_Pin; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); @@ -278,16 +280,15 @@ void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle) PA4 ------> ADC2_IN4 PA5 ------> ADC2_IN5 PA6 ------> ADC2_IN6 - PC4 ------> ADC2_IN14 PC5 ------> ADC2_IN15 */ GPIO_InitStruct.Pin = M0_IB_Pin|M0_IC_Pin|M1_IC_Pin|M1_IB_Pin - |AUX_TEMP_Pin|M0_TEMP_Pin; + |M0_TEMP_Pin; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); - GPIO_InitStruct.Pin = M1_TEMP_Pin|AUX_I_Pin|VBUS_S_Pin; + GPIO_InitStruct.Pin = M1_TEMP_Pin|AUX_TEMP_Pin|VBUS_S_Pin; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); @@ -346,13 +347,12 @@ void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle) PA4 ------> ADC1_IN4 PA5 ------> ADC1_IN5 PA6 ------> ADC1_IN6 - PC4 ------> ADC1_IN14 PC5 ------> ADC1_IN15 */ HAL_GPIO_DeInit(GPIOC, M0_IB_Pin|M0_IC_Pin|M1_IC_Pin|M1_IB_Pin - |AUX_TEMP_Pin|M0_TEMP_Pin); + |M0_TEMP_Pin); - HAL_GPIO_DeInit(GPIOA, M1_TEMP_Pin|AUX_I_Pin|VBUS_S_Pin); + HAL_GPIO_DeInit(GPIOA, M1_TEMP_Pin|AUX_TEMP_Pin|VBUS_S_Pin); /* ADC1 interrupt Deinit */ /* USER CODE BEGIN ADC1:ADC_IRQn disable */ @@ -383,13 +383,12 @@ void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle) PA4 ------> ADC2_IN4 PA5 ------> ADC2_IN5 PA6 ------> ADC2_IN6 - PC4 ------> ADC2_IN14 PC5 ------> ADC2_IN15 */ HAL_GPIO_DeInit(GPIOC, M0_IB_Pin|M0_IC_Pin|M1_IC_Pin|M1_IB_Pin - |AUX_TEMP_Pin|M0_TEMP_Pin); + |M0_TEMP_Pin); - HAL_GPIO_DeInit(GPIOA, M1_TEMP_Pin|AUX_I_Pin|VBUS_S_Pin); + HAL_GPIO_DeInit(GPIOA, M1_TEMP_Pin|AUX_TEMP_Pin|VBUS_S_Pin); /* ADC2 interrupt Deinit */ /* USER CODE BEGIN ADC2:ADC_IRQn disable */ diff --git a/Firmware/Board/v3/Src/gpio.c b/Firmware/Board/v3/Src/gpio.c index 9585749f..91b3f3cd 100644 --- a/Firmware/Board/v3/Src/gpio.c +++ b/Firmware/Board/v3/Src/gpio.c @@ -55,6 +55,9 @@ #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 1 \ || HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 2 #include "prev_board_ver/gpio_V3_2.c" +#elif HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 3 \ +|| HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 4 +#include "prev_board_ver/gpio_V3_4.c" #else /* USER CODE END 0 */ @@ -87,19 +90,22 @@ void MX_GPIO_Init(void) /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOC, M0_nCS_Pin|M1_nCS_Pin, GPIO_PIN_SET); - /*Configure GPIO pin Output Level */ - HAL_GPIO_WritePin(GPIOC, M1_DC_CAL_Pin|M0_DC_CAL_Pin, GPIO_PIN_RESET); - /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(EN_GATE_GPIO_Port, EN_GATE_Pin, GPIO_PIN_RESET); - /*Configure GPIO pins : PCPin PCPin PCPin PCPin */ - GPIO_InitStruct.Pin = M0_nCS_Pin|M1_nCS_Pin|M1_DC_CAL_Pin|M0_DC_CAL_Pin; + /*Configure GPIO pins : PCPin PCPin */ + GPIO_InitStruct.Pin = M0_nCS_Pin|M1_nCS_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); + /*Configure GPIO pins : PCPin PCPin PCPin */ + GPIO_InitStruct.Pin = M1_ENC_Z_Pin|GPIO_5_Pin|M0_ENC_Z_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); + /*Configure GPIO pin : PtPin */ GPIO_InitStruct.Pin = GPIO_3_Pin; GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; @@ -107,13 +113,13 @@ void MX_GPIO_Init(void) HAL_GPIO_Init(GPIO_3_GPIO_Port, &GPIO_InitStruct); /*Configure GPIO pins : PAPin PAPin */ - GPIO_InitStruct.Pin = GPIO_4_Pin|M0_ENC_Z_Pin; + GPIO_InitStruct.Pin = GPIO_4_Pin|GPIO_7_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); /*Configure GPIO pins : PBPin PBPin */ - GPIO_InitStruct.Pin = GPIO_5_Pin|M1_ENC_Z_Pin; + GPIO_InitStruct.Pin = GPIO_6_Pin|GPIO_8_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); diff --git a/Firmware/Board/v3/Src/prev_board_ver/adc_V3_4.c b/Firmware/Board/v3/Src/prev_board_ver/adc_V3_4.c new file mode 100644 index 00000000..49862c97 --- /dev/null +++ b/Firmware/Board/v3/Src/prev_board_ver/adc_V3_4.c @@ -0,0 +1,375 @@ + +ADC_HandleTypeDef hadc1; +ADC_HandleTypeDef hadc2; +ADC_HandleTypeDef hadc3; + +/* ADC1 init function */ +void MX_ADC1_Init(void) +{ + ADC_ChannelConfTypeDef sConfig; + ADC_InjectionConfTypeDef sConfigInjected; + + /**Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) + */ + hadc1.Instance = ADC1; + hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; + hadc1.Init.Resolution = ADC_RESOLUTION_12B; + hadc1.Init.ScanConvMode = DISABLE; + hadc1.Init.ContinuousConvMode = DISABLE; + hadc1.Init.DiscontinuousConvMode = DISABLE; + hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; + hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; + hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; + hadc1.Init.NbrOfConversion = 1; + hadc1.Init.DMAContinuousRequests = DISABLE; + hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; + if (HAL_ADC_Init(&hadc1) != HAL_OK) + { + _Error_Handler(__FILE__, __LINE__); + } + + /**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time. + */ + sConfig.Channel = ADC_CHANNEL_6; + sConfig.Rank = 1; + sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES; + if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) + { + _Error_Handler(__FILE__, __LINE__); + } + + /**Configures for the selected ADC injected channel its corresponding rank in the sequencer and its sample time + */ + sConfigInjected.InjectedChannel = ADC_CHANNEL_6; + sConfigInjected.InjectedRank = 1; + sConfigInjected.InjectedNbrOfConversion = 1; + sConfigInjected.InjectedSamplingTime = ADC_SAMPLETIME_3CYCLES; + sConfigInjected.ExternalTrigInjecConvEdge = ADC_EXTERNALTRIGINJECCONVEDGE_RISING; + sConfigInjected.ExternalTrigInjecConv = ADC_EXTERNALTRIGINJECCONV_T1_TRGO; + sConfigInjected.AutoInjectedConv = DISABLE; + sConfigInjected.InjectedDiscontinuousConvMode = DISABLE; + sConfigInjected.InjectedOffset = 0; + if (HAL_ADCEx_InjectedConfigChannel(&hadc1, &sConfigInjected) != HAL_OK) + { + _Error_Handler(__FILE__, __LINE__); + } + +} +/* ADC2 init function */ +void MX_ADC2_Init(void) +{ + ADC_ChannelConfTypeDef sConfig; + ADC_InjectionConfTypeDef sConfigInjected; + + /**Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) + */ + hadc2.Instance = ADC2; + hadc2.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; + hadc2.Init.Resolution = ADC_RESOLUTION_12B; + hadc2.Init.ScanConvMode = DISABLE; + hadc2.Init.ContinuousConvMode = DISABLE; + hadc2.Init.DiscontinuousConvMode = DISABLE; + hadc2.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING; + hadc2.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T8_TRGO; + hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT; + hadc2.Init.NbrOfConversion = 1; + hadc2.Init.DMAContinuousRequests = DISABLE; + hadc2.Init.EOCSelection = ADC_EOC_SINGLE_CONV; + if (HAL_ADC_Init(&hadc2) != HAL_OK) + { + _Error_Handler(__FILE__, __LINE__); + } + + /**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time. + */ + sConfig.Channel = ADC_CHANNEL_13; + sConfig.Rank = 1; + sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES; + if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK) + { + _Error_Handler(__FILE__, __LINE__); + } + + /**Configures for the selected ADC injected channel its corresponding rank in the sequencer and its sample time + */ + sConfigInjected.InjectedChannel = ADC_CHANNEL_10; + sConfigInjected.InjectedRank = 1; + sConfigInjected.InjectedNbrOfConversion = 1; + sConfigInjected.InjectedSamplingTime = ADC_SAMPLETIME_3CYCLES; + sConfigInjected.ExternalTrigInjecConvEdge = ADC_EXTERNALTRIGINJECCONVEDGE_RISING; + sConfigInjected.ExternalTrigInjecConv = ADC_EXTERNALTRIGINJECCONV_T1_TRGO; + sConfigInjected.AutoInjectedConv = DISABLE; + sConfigInjected.InjectedDiscontinuousConvMode = DISABLE; + sConfigInjected.InjectedOffset = 0; + if (HAL_ADCEx_InjectedConfigChannel(&hadc2, &sConfigInjected) != HAL_OK) + { + _Error_Handler(__FILE__, __LINE__); + } + +} +/* ADC3 init function */ +void MX_ADC3_Init(void) +{ + ADC_ChannelConfTypeDef sConfig; + ADC_InjectionConfTypeDef sConfigInjected; + + /**Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) + */ + hadc3.Instance = ADC3; + hadc3.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; + hadc3.Init.Resolution = ADC_RESOLUTION_12B; + hadc3.Init.ScanConvMode = DISABLE; + hadc3.Init.ContinuousConvMode = DISABLE; + hadc3.Init.DiscontinuousConvMode = DISABLE; + hadc3.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING; + hadc3.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T8_TRGO; + hadc3.Init.DataAlign = ADC_DATAALIGN_RIGHT; + hadc3.Init.NbrOfConversion = 1; + hadc3.Init.DMAContinuousRequests = DISABLE; + hadc3.Init.EOCSelection = ADC_EOC_SINGLE_CONV; + if (HAL_ADC_Init(&hadc3) != HAL_OK) + { + _Error_Handler(__FILE__, __LINE__); + } + + /**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time. + */ + sConfig.Channel = ADC_CHANNEL_12; + sConfig.Rank = 1; + sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES; + if (HAL_ADC_ConfigChannel(&hadc3, &sConfig) != HAL_OK) + { + _Error_Handler(__FILE__, __LINE__); + } + + /**Configures for the selected ADC injected channel its corresponding rank in the sequencer and its sample time + */ + sConfigInjected.InjectedChannel = ADC_CHANNEL_11; + sConfigInjected.InjectedRank = 1; + sConfigInjected.InjectedNbrOfConversion = 1; + sConfigInjected.InjectedSamplingTime = ADC_SAMPLETIME_3CYCLES; + sConfigInjected.ExternalTrigInjecConvEdge = ADC_EXTERNALTRIGINJECCONVEDGE_RISING; + sConfigInjected.ExternalTrigInjecConv = ADC_EXTERNALTRIGINJECCONV_T1_TRGO; + sConfigInjected.AutoInjectedConv = DISABLE; + sConfigInjected.InjectedDiscontinuousConvMode = DISABLE; + sConfigInjected.InjectedOffset = 0; + if (HAL_ADCEx_InjectedConfigChannel(&hadc3, &sConfigInjected) != HAL_OK) + { + _Error_Handler(__FILE__, __LINE__); + } + +} + +void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle) +{ + + GPIO_InitTypeDef GPIO_InitStruct; + if(adcHandle->Instance==ADC1) + { + /* USER CODE BEGIN ADC1_MspInit 0 */ + + /* USER CODE END ADC1_MspInit 0 */ + /* ADC1 clock enable */ + __HAL_RCC_ADC1_CLK_ENABLE(); + + /**ADC1 GPIO Configuration + PC0 ------> ADC1_IN10 + PC1 ------> ADC1_IN11 + PC2 ------> ADC1_IN12 + PC3 ------> ADC1_IN13 + PA4 ------> ADC1_IN4 + PA5 ------> ADC1_IN5 + PA6 ------> ADC1_IN6 + PC4 ------> ADC1_IN14 + PC5 ------> ADC1_IN15 + */ + GPIO_InitStruct.Pin = M0_IB_Pin|M0_IC_Pin|M1_IC_Pin|M1_IB_Pin + |AUX_TEMP_Pin|M0_TEMP_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = M1_TEMP_Pin|AUX_I_Pin|VBUS_S_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + + /* ADC1 interrupt Init */ + HAL_NVIC_SetPriority(ADC_IRQn, 5, 0); + HAL_NVIC_EnableIRQ(ADC_IRQn); + /* USER CODE BEGIN ADC1_MspInit 1 */ + + /* USER CODE END ADC1_MspInit 1 */ + } + else if(adcHandle->Instance==ADC2) + { + /* USER CODE BEGIN ADC2_MspInit 0 */ + + /* USER CODE END ADC2_MspInit 0 */ + /* ADC2 clock enable */ + __HAL_RCC_ADC2_CLK_ENABLE(); + + /**ADC2 GPIO Configuration + PC0 ------> ADC2_IN10 + PC1 ------> ADC2_IN11 + PC2 ------> ADC2_IN12 + PC3 ------> ADC2_IN13 + PA4 ------> ADC2_IN4 + PA5 ------> ADC2_IN5 + PA6 ------> ADC2_IN6 + PC4 ------> ADC2_IN14 + PC5 ------> ADC2_IN15 + */ + GPIO_InitStruct.Pin = M0_IB_Pin|M0_IC_Pin|M1_IC_Pin|M1_IB_Pin + |AUX_TEMP_Pin|M0_TEMP_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); + + GPIO_InitStruct.Pin = M1_TEMP_Pin|AUX_I_Pin|VBUS_S_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + + /* ADC2 interrupt Init */ + HAL_NVIC_SetPriority(ADC_IRQn, 5, 0); + HAL_NVIC_EnableIRQ(ADC_IRQn); + /* USER CODE BEGIN ADC2_MspInit 1 */ + + /* USER CODE END ADC2_MspInit 1 */ + } + else if(adcHandle->Instance==ADC3) + { + /* USER CODE BEGIN ADC3_MspInit 0 */ + + /* USER CODE END ADC3_MspInit 0 */ + /* ADC3 clock enable */ + __HAL_RCC_ADC3_CLK_ENABLE(); + + /**ADC3 GPIO Configuration + PC0 ------> ADC3_IN10 + PC1 ------> ADC3_IN11 + PC2 ------> ADC3_IN12 + PC3 ------> ADC3_IN13 + */ + GPIO_InitStruct.Pin = M0_IB_Pin|M0_IC_Pin|M1_IC_Pin|M1_IB_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); + + /* ADC3 interrupt Init */ + HAL_NVIC_SetPriority(ADC_IRQn, 5, 0); + HAL_NVIC_EnableIRQ(ADC_IRQn); + /* USER CODE BEGIN ADC3_MspInit 1 */ + + /* USER CODE END ADC3_MspInit 1 */ + } +} + +void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle) +{ + + if(adcHandle->Instance==ADC1) + { + /* USER CODE BEGIN ADC1_MspDeInit 0 */ + + /* USER CODE END ADC1_MspDeInit 0 */ + /* Peripheral clock disable */ + __HAL_RCC_ADC1_CLK_DISABLE(); + + /**ADC1 GPIO Configuration + PC0 ------> ADC1_IN10 + PC1 ------> ADC1_IN11 + PC2 ------> ADC1_IN12 + PC3 ------> ADC1_IN13 + PA4 ------> ADC1_IN4 + PA5 ------> ADC1_IN5 + PA6 ------> ADC1_IN6 + PC4 ------> ADC1_IN14 + PC5 ------> ADC1_IN15 + */ + HAL_GPIO_DeInit(GPIOC, M0_IB_Pin|M0_IC_Pin|M1_IC_Pin|M1_IB_Pin + |AUX_TEMP_Pin|M0_TEMP_Pin); + + HAL_GPIO_DeInit(GPIOA, M1_TEMP_Pin|AUX_I_Pin|VBUS_S_Pin); + + /* ADC1 interrupt Deinit */ + /* USER CODE BEGIN ADC1:ADC_IRQn disable */ + /** + * Uncomment the line below to disable the "ADC_IRQn" interrupt + * Be aware, disabling shared interrupt may affect other IPs + */ + /* HAL_NVIC_DisableIRQ(ADC_IRQn); */ + /* USER CODE END ADC1:ADC_IRQn disable */ + + /* USER CODE BEGIN ADC1_MspDeInit 1 */ + + /* USER CODE END ADC1_MspDeInit 1 */ + } + else if(adcHandle->Instance==ADC2) + { + /* USER CODE BEGIN ADC2_MspDeInit 0 */ + + /* USER CODE END ADC2_MspDeInit 0 */ + /* Peripheral clock disable */ + __HAL_RCC_ADC2_CLK_DISABLE(); + + /**ADC2 GPIO Configuration + PC0 ------> ADC2_IN10 + PC1 ------> ADC2_IN11 + PC2 ------> ADC2_IN12 + PC3 ------> ADC2_IN13 + PA4 ------> ADC2_IN4 + PA5 ------> ADC2_IN5 + PA6 ------> ADC2_IN6 + PC4 ------> ADC2_IN14 + PC5 ------> ADC2_IN15 + */ + HAL_GPIO_DeInit(GPIOC, M0_IB_Pin|M0_IC_Pin|M1_IC_Pin|M1_IB_Pin + |AUX_TEMP_Pin|M0_TEMP_Pin); + + HAL_GPIO_DeInit(GPIOA, M1_TEMP_Pin|AUX_I_Pin|VBUS_S_Pin); + + /* ADC2 interrupt Deinit */ + /* USER CODE BEGIN ADC2:ADC_IRQn disable */ + /** + * Uncomment the line below to disable the "ADC_IRQn" interrupt + * Be aware, disabling shared interrupt may affect other IPs + */ + /* HAL_NVIC_DisableIRQ(ADC_IRQn); */ + /* USER CODE END ADC2:ADC_IRQn disable */ + + /* USER CODE BEGIN ADC2_MspDeInit 1 */ + + /* USER CODE END ADC2_MspDeInit 1 */ + } + else if(adcHandle->Instance==ADC3) + { + /* USER CODE BEGIN ADC3_MspDeInit 0 */ + + /* USER CODE END ADC3_MspDeInit 0 */ + /* Peripheral clock disable */ + __HAL_RCC_ADC3_CLK_DISABLE(); + + /**ADC3 GPIO Configuration + PC0 ------> ADC3_IN10 + PC1 ------> ADC3_IN11 + PC2 ------> ADC3_IN12 + PC3 ------> ADC3_IN13 + */ + HAL_GPIO_DeInit(GPIOC, M0_IB_Pin|M0_IC_Pin|M1_IC_Pin|M1_IB_Pin); + + /* ADC3 interrupt Deinit */ + /* USER CODE BEGIN ADC3:ADC_IRQn disable */ + /** + * Uncomment the line below to disable the "ADC_IRQn" interrupt + * Be aware, disabling shared interrupt may affect other IPs + */ + /* HAL_NVIC_DisableIRQ(ADC_IRQn); */ + /* USER CODE END ADC3:ADC_IRQn disable */ + + /* USER CODE BEGIN ADC3_MspDeInit 1 */ + + /* USER CODE END ADC3_MspDeInit 1 */ + } +} diff --git a/Firmware/Board/v3/Src/prev_board_ver/gpio_V3_4.c b/Firmware/Board/v3/Src/prev_board_ver/gpio_V3_4.c new file mode 100644 index 00000000..075be197 --- /dev/null +++ b/Firmware/Board/v3/Src/prev_board_ver/gpio_V3_4.c @@ -0,0 +1,71 @@ +/** Configure pins as + * Analog + * Input + * Output + * EVENT_OUT + * EXTI +*/ +void MX_GPIO_Init(void) +{ + + GPIO_InitTypeDef GPIO_InitStruct; + + /* GPIO Ports Clock Enable */ + __HAL_RCC_GPIOC_CLK_ENABLE(); + __HAL_RCC_GPIOH_CLK_ENABLE(); + __HAL_RCC_GPIOA_CLK_ENABLE(); + __HAL_RCC_GPIOB_CLK_ENABLE(); + __HAL_RCC_GPIOD_CLK_ENABLE(); + + /*Configure GPIO pin Output Level */ + HAL_GPIO_WritePin(GPIOC, M0_nCS_Pin|M1_nCS_Pin, GPIO_PIN_SET); + + /*Configure GPIO pin Output Level */ + HAL_GPIO_WritePin(GPIOC, M1_DC_CAL_Pin|M0_DC_CAL_Pin, GPIO_PIN_RESET); + + /*Configure GPIO pin Output Level */ + HAL_GPIO_WritePin(EN_GATE_GPIO_Port, EN_GATE_Pin, GPIO_PIN_RESET); + + /*Configure GPIO pins : PCPin PCPin PCPin PCPin */ + GPIO_InitStruct.Pin = M0_nCS_Pin|M1_nCS_Pin|M1_DC_CAL_Pin|M0_DC_CAL_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); + + /*Configure GPIO pin : PtPin */ + GPIO_InitStruct.Pin = GPIO_3_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; + GPIO_InitStruct.Pull = GPIO_PULLDOWN; + HAL_GPIO_Init(GPIO_3_GPIO_Port, &GPIO_InitStruct); + + /*Configure GPIO pins : PAPin PAPin */ + GPIO_InitStruct.Pin = GPIO_4_Pin|M0_ENC_Z_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + + /*Configure GPIO pins : PBPin PBPin */ + GPIO_InitStruct.Pin = GPIO_5_Pin|M1_ENC_Z_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); + + /*Configure GPIO pin : PtPin */ + GPIO_InitStruct.Pin = EN_GATE_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; + HAL_GPIO_Init(EN_GATE_GPIO_Port, &GPIO_InitStruct); + + /*Configure GPIO pin : PtPin */ + GPIO_InitStruct.Pin = nFAULT_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = GPIO_PULLUP; + HAL_GPIO_Init(nFAULT_GPIO_Port, &GPIO_InitStruct); + + /* EXTI interrupt init*/ + HAL_NVIC_SetPriority(EXTI2_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(EXTI2_IRQn); + +} From ab27552c0ebf7e08d626b5b4c001ebabaddcfc01 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 9 Apr 2018 17:26:51 -0700 Subject: [PATCH 049/215] add v3.5 support to build system --- Firmware/README.md | 2 +- Firmware/Tupfile.lua | 8 ++++++++ Firmware/tup.config.default | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Firmware/README.md b/Firmware/README.md index 0346a9bf..39f6b948 100644 --- a/Firmware/README.md +++ b/Firmware/README.md @@ -30,7 +30,7 @@ In this section we will set the compile-time parameters, later we will also set To customize the compile time parameters, copy or rename the file `Firmware/tup.config.default` to `Firmware/tup.config` and edit the parameters in that file: -__CONFIG_BOARD_VERSION__: The board version you're using. Can be `v3.1`, `v3.2`, `v3.3`, `v3.4-24V` or `v3.4-48V`. Check for a label on the upper side of the ODrive to find out which version you have. +__CONFIG_BOARD_VERSION__: The board version you're using. Can be `v3.1`, `v3.2`, `v3.3`, `v3.4-24V`, `v3.4-48V`, `v3.5-24V` or `v3.5-48V`. Check for a label on the upper side of the ODrive to find out which version you have. __CONFIG_USB_PROTOCOL__: Defines which protocol the ODrive should use on the USB interface. * `native`: The native ODrive protocol. Use this if you want to use the python tools in this repo. diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index aa122ca5..b57acb42 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -23,6 +23,14 @@ elseif boardversion == "v3.4-48V" then boarddir = 'Board/v3' FLAGS += "-DHW_VERSION_MAJOR=3 -DHW_VERSION_MINOR=4" FLAGS += "-DHW_VERSION_VOLTAGE=48" +elseif boardversion == "v3.5-24V" then + boarddir = 'Board/v3' + FLAGS += "-DHW_VERSION_MAJOR=3 -DHW_VERSION_MINOR=5" + FLAGS += "-DHW_VERSION_VOLTAGE=24" +elseif boardversion == "v3.5-48V" then + boarddir = 'Board/v3' + FLAGS += "-DHW_VERSION_MAJOR=3 -DHW_VERSION_MINOR=5" + FLAGS += "-DHW_VERSION_VOLTAGE=48" elseif boardversion == "" then error("board version not specified - take a look at tup.config.default") else diff --git a/Firmware/tup.config.default b/Firmware/tup.config.default index be0515fc..5cd89434 100644 --- a/Firmware/tup.config.default +++ b/Firmware/tup.config.default @@ -1,6 +1,6 @@ # Copy this file to tup.config and adapt it to your needs # make sure this fits your board -#CONFIG_BOARD_VERSION=v3.4-24V +#CONFIG_BOARD_VERSION=v3.5-24V CONFIG_USB_PROTOCOL=native CONFIG_UART_PROTOCOL=ascii CONFIG_STEP_DIR=n From 09f361de61d4670e5f94c52df52e1672a5c478cd Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 9 Apr 2018 19:32:36 -0700 Subject: [PATCH 050/215] fix encoder offset calibration and error handling --- Firmware/MotorControl/axis.cpp | 6 ++++++ Firmware/MotorControl/encoder.cpp | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index fe3760aa..c0488dfa 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -251,14 +251,20 @@ void Axis::run_state_machine_loop() { switch (current_state_) { case AXIS_STATE_MOTOR_CALIBRATION: status = motor_.run_calibration(); + if (!status) + error_ |= ERROR_MOTOR_FAILED; break; case AXIS_STATE_ENCODER_INDEX_SEARCH: status = encoder_.run_index_search(); + if (!status) + error_ |= ERROR_ENCODER_FAILED; break; case AXIS_STATE_ENCODER_OFFSET_CALIBRATION: status = encoder_.run_offset_calibration(); + if (!status) + error_ |= ERROR_ENCODER_FAILED; break; case AXIS_STATE_SENSORLESS_CONTROL: diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 19a39792..fad27267 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -103,7 +103,7 @@ bool Encoder::run_offset_calibration() { // Temporarily disable index search so it doesn't mess // with the offset calibration bool old_use_index = config_.use_index; - config_.use_index = true; + config_.use_index = false; float voltage_magnitude; if (axis_->motor_.config_.motor_type == MOTOR_TYPE_HIGH_CURRENT) From 282d824545a7f6960817f36c6d32b665405ef895 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 9 Apr 2018 19:35:56 -0700 Subject: [PATCH 051/215] improve error detection --- tools/odrive/tests.py | 12 ++++++++++-- tools/run_tests.py | 6 +++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index 0cdc121f..3454b56a 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -64,6 +64,8 @@ def test_assert_no_error(axis_ctx: AxisTestContext): errors.append("sensorless_estimator failed with error {:04X}".format(axis_ctx.handle.sensorless_estimator.error)) if axis_ctx.handle.error != 0: errors.append("axis failed with error {:04X}".format(axis_ctx.handle.error)) + elif len(errors) > 0: + errors.append("and by the way: axis reports no error even though there is one") if len(errors) > 0: raise TestFailed("\n".join(errors)) @@ -175,6 +177,12 @@ class TestFlashAndErase(ODriveTest): def __init__(self): ODriveTest.__init__(self, exclusive=True) def run_test(self, odrv_ctx: ODriveTestContext, logger): + # Set board-version and compile + with open("tup.config", mode="w") as tup_config: + tup_config.write("CONFIG_STRICT=true\n") + tup_config.write("CONFIG_BOARD_VERSION={}\n".format(odrv_ctx.yaml['board-version'])) + #exit(1) + run("make", logger, timeout=10) run("make flash PROGRAMMER='" + odrv_ctx.yaml['programmer'] + "'", logger, timeout=20) # FIXME: device does not reboot correctly after erasing config this way #run("make erase_config PROGRAMMER='" + test_rig.programmer + "'", timeout=10) @@ -248,7 +256,7 @@ class TestMotorCalibration(AxisTest): request_state(axis_ctx, AXIS_STATE_MOTOR_CALIBRATION) time.sleep(6) test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) - test_assert_eq(axis_ctx.handle.error, AXIS_ERROR_NO_ERROR) + test_assert_no_error(axis_ctx) test_assert_eq(axis_ctx.handle.motor.config.phase_resistance, axis_ctx.yaml['motor-phase-resistance'], accuracy=0.2) test_assert_eq(axis_ctx.handle.motor.config.phase_inductance, axis_ctx.yaml['motor-phase-inductance'], accuracy=0.5) axis_ctx.handle.motor.config.pre_calibrated = True @@ -282,7 +290,7 @@ class TestEncoderOffsetCalibration(AxisTest): # TODO: ensure the encoder calibration doesn't do crap time.sleep(11) test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) - test_assert_eq(axis_ctx.handle.error, AXIS_ERROR_NO_ERROR) + test_assert_no_error(axis_ctx) test_assert_eq(axis_ctx.handle.motor.config.direction, axis_ctx.yaml['motor-direction']) axis_ctx.handle.encoder.config.pre_calibrated = True diff --git a/tools/run_tests.py b/tools/run_tests.py index 968000e6..394201d0 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -84,7 +84,7 @@ try: for odrv in odrives_by_name: odrv_test_thread(odrv) else: - for_all_parallel(odrives_by_name, lambda x: x, odrv_test_thread) + for_all_parallel(odrives_by_name, lambda x: type(test).__name__ + " on " + x, odrv_test_thread) elif isinstance(test, AxisTest): def axis_test_thread(axis_name): @@ -112,7 +112,7 @@ try: for conflicting_axis in conflicting_axes: conflicting_axis.lock.release() - for_all_parallel(axes_by_name, lambda x: x, axis_test_thread) + for_all_parallel(axes_by_name, lambda x: type(test).__name__ + " on " + x, axis_test_thread) elif isinstance(test, DualAxisTest): def dual_axis_test_thread(coupling): @@ -138,7 +138,7 @@ try: for axis_ctx in coupled_axes: axis_ctx.lock.release() - for_all_parallel(couplings, lambda x: "..".join([a.name for a in x]), dual_axis_test_thread) + for_all_parallel(couplings, lambda x: type(test).__name__ + " on " + "..".join([a.name for a in x]), dual_axis_test_thread) else: logger.warn("ignoring unknown test type {}".format(type(test))) From ab5e68697559794b95fc5b8484d26d4655870b07 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 9 Apr 2018 19:38:19 -0700 Subject: [PATCH 052/215] upgrade test-rig.yaml to v3.5 --- tools/test-rig.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/test-rig.yaml b/tools/test-rig.yaml index 05e26696..2c05cc9d 100644 --- a/tools/test-rig.yaml +++ b/tools/test-rig.yaml @@ -2,8 +2,8 @@ # ODrives odrives: - name: top-odrive - board-version: v3.4-24V - serial-number: "385F324D3037" + board-version: v3.5-48V + serial-number: "3660335E3037" brake-resistance: 0.47 uart: /dev/serial/by-id/[not-yet-used] usb: auto @@ -26,8 +26,8 @@ odrives: motor-max-current: 50 encoder-cpr: 8192 - name: bottom-odrive - board-version: v3.4-48V - serial-number: "306A396A3235" + board-version: v3.5-24V + serial-number: "3661335E3037" brake-resistance: 0.47 uart: /dev/serial/by-id/[not-yet-used] usb: auto From fa8f4e99151cd04bac8a88e6a2e9039fd5a27b75 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 9 Apr 2018 21:48:55 -0700 Subject: [PATCH 053/215] improve response time for auto-securing the test rig when something goes wrong --- tools/run_tests.py | 53 +++++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/tools/run_tests.py b/tools/run_tests.py index 394201d0..dca6b39d 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -13,7 +13,7 @@ import sys import threading import traceback from odrive.tests import * -from odrive.utils import Logger, for_all_parallel +from odrive.utils import Logger, for_all_parallel, Event all_tests = [ @@ -65,6 +65,7 @@ else: for coupling in test_rig_yaml['couplings']: couplings.append([axes_by_name[axis_name] for axis_name in coupling]) +app_shutdown_token = Event() try: for test in all_tests: @@ -98,15 +99,21 @@ try: for conflicting_axis in conflicting_axes: conflicting_axis.lock.acquire() try: - # Run test on this axis - logger.info('● running {} on {}...'.format(type(test).__name__, axis_name)) - try: - test.check_preconditions(axis_ctx, + if not app_shutdown_token.is_set(): + # Run test on this axis + logger.info('● running {} on {}...'.format(type(test).__name__, axis_name)) + try: + test.check_preconditions(axis_ctx, + logger.indent(' {}: '.format(axis_name))) + except: + raise PreconditionsNotMet() + test.run_test(axis_ctx, logger.indent(' {}: '.format(axis_name))) - except: - raise PreconditionsNotMet() - test.run_test(axis_ctx, - logger.indent(' {}: '.format(axis_name))) + else: + logger.warn('⬛ skipping {} on {}'.format(type(test).__name__, axis_name)) + except: + app_shutdown_token.set() + raise finally: # Release all conflicting axes for conflicting_axis in conflicting_axes: @@ -124,15 +131,21 @@ try: for axis_ctx in coupled_axes: axis_ctx.lock.acquire() try: - # Run test on this axis - logger.info('● running {} on {}...'.format(type(test).__name__, coupling_name)) - try: - test.check_preconditions(coupled_axes[0], coupled_axes[1], + if not app_shutdown_token.is_set(): + # Run test on this axis + logger.info('● running {} on {}...'.format(type(test).__name__, coupling_name)) + try: + test.check_preconditions(coupled_axes[0], coupled_axes[1], + logger.indent(' {}: '.format(coupling_name))) + except: + raise PreconditionsNotMet() + test.run_test(coupled_axes[0], coupled_axes[1], logger.indent(' {}: '.format(coupling_name))) - except: - raise PreconditionsNotMet() - test.run_test(coupled_axes[0], coupled_axes[1], - logger.indent(' {}: '.format(coupling_name))) + else: + logger.warn('⬛ skipping {} on {}...'.format(type(test).__name__, coupling_name)) + except: + app_shutdown_token.set() + raise finally: # Release all conflicting axes for axis_ctx in coupled_axes: @@ -147,11 +160,13 @@ except: logger.error(traceback.format_exc()) logger.debug('=> Test failed. Please wait while I secure the test rig...') try: - dont_secure_after_failure = True # TODO: disable + dont_secure_after_failure = False # TODO: disable if not dont_secure_after_failure: def odrv_reset_thread(odrv_name): odrv_ctx = odrives_by_name[odrv_name] - run("make erase PROGRAMMER='" + odrv_ctx.yaml['programmer'] + "'", logger, timeout=30) + #run("make erase PROGRAMMER='" + odrv_ctx.yaml['programmer'] + "'", logger, timeout=30) + odrv_ctx.handle.axis0.requested_state = AXIS_STATE_IDLE + odrv_ctx.handle.axis1.requested_state = AXIS_STATE_IDLE for_all_parallel(odrives_by_name, lambda x: x['name'], odrv_reset_thread) except: logger.error('///////////////////////////////////////////') From 69b06dde89f2f0058bd3e9b45e464a61856b7bad Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 9 Apr 2018 21:50:52 -0700 Subject: [PATCH 054/215] implement high velocity test --- tools/odrive/tests.py | 78 +++++++++++++++++++++++++++++++++++++++++++ tools/run_tests.py | 3 +- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index 3454b56a..f13c5d71 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -353,6 +353,84 @@ class TestStoreAndReboot(ODriveTest): test_assert_eq(axis_ctx.handle.motor.config.phase_resistance, axis_ctx.yaml['motor-phase-resistance'], accuracy=0.15) test_assert_eq(axis_ctx.handle.motor.config.phase_inductance, axis_ctx.yaml['motor-phase-inductance'], accuracy=0.5) + +class TestHighVelocityCtrl(AxisTest): + """ + Spins the motor up to it's max speed during a period of 10s. + The commanded max speed is based on the motor's KV rating and nominal V_bus, + however due to several factors the theoretical limit is about 72% of that. + The test passes if the motor follows the commanded ramp closely up to 90% of + the theoretical limit (and if no errors occur along the way). + """ + def check_preconditions(self, axis_ctx: AxisTestContext, logger): + super(TestHighVelocityCtrl, self).check_preconditions(axis_ctx, logger) + test_assert_eq(axis_ctx.handle.motor.is_calibrated, True) + test_assert_eq(axis_ctx.handle.encoder.is_ready, True) + + def run_test(self, axis_ctx: AxisTestContext, logger): + # Calculate theoretical max velocity in encoder counts per second based on the nominal + # V_bus and motor KV rating + max_rpm = axis_ctx.odrv_ctx.yaml['vbus-voltage'] * axis_ctx.yaml['motor-kv'] + rated_limit = max_rpm / 60 * axis_ctx.yaml['encoder-cpr'] + expected_limit = rated_limit + + # The KV-rating assumes square-waves on the motor phases (hexagonal space vector trajectory) + # whereas the ODrive modulates the space vector around a circular trajectory. + # See Fig 4.28 here: http://krex.k-state.edu/dspace/bitstream/handle/2097/1507/JamesMevey2009.pdf + expected_limit *= (2/math.sqrt(3)) / (4/math.pi) # roughtly 90% + + # The ODrive only goes to 80% modulation depth in order to save some time for the ADC measurements. + # See FOC_current in motor.cpp. + expected_limit *= 0.8 + + # Add a 10% margin to account for + expected_limit *= 0.9 + + logger.debug("rated max speed: {}, expected max speed: >= {}".format(rated_limit, expected_limit)) + #theoretical_limit = 100000 + + # Set the current limit accordingly so we don't burn the brake resistor while slowing down + set_limits(axis_ctx, logger, vel_limit=rated_limit, current_limit=50) + request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) + + + ramp_up_time = 20.0 + t_0 = time.monotonic() + last_print = t_0 + max_true_vel = 0.0 + while True: + ratio = (time.monotonic() - t_0) / ramp_up_time + if ratio >= 1: + break + + # While ramping up we want to remain within +-5% of the setpoint. + # However we accept if we can only approach 80% of the theoretical limit. + vel_setpoint = ratio * rated_limit + vel_range = max(0.05*vel_setpoint, 5000) + if vel_setpoint - vel_range > expected_limit: + vel_range = vel_setpoint - expected_limit + + # log progress + if time.monotonic() - last_print > 1: + last_print = time.monotonic() + logger.debug("ramping up: now at " + str(vel_setpoint)) + + # set and measure velocity + axis_ctx.handle.controller.set_vel_setpoint(vel_setpoint, 0) + true_vel = axis_ctx.handle.encoder.pll_vel + max_true_vel = max(true_vel, max_true_vel) + test_assert_eq(true_vel, vel_setpoint, range=vel_range) + test_assert_no_error(axis_ctx) + + time.sleep(0.001) + + axis_ctx.handle.controller.set_vel_setpoint(0, 0) + time.sleep(0.5) + # TODO: this is not a good bound, but the encoder float resolution results in a bad velocity estimate after this many turns + test_assert_eq(axis_ctx.handle.encoder.pll_vel, 0, range=2000) + request_state(axis_ctx, AXIS_STATE_IDLE) + + class TestVelCtrlVsPosCtrl(DualAxisTest): """ Uses one ODrive as a load operating in velocity control mode. diff --git a/tools/run_tests.py b/tools/run_tests.py index dca6b39d..f0ee3bb1 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -29,7 +29,8 @@ all_tests = [ # TestClosedLoopControl(), TestDiscoverAndGotoIdle(), # for testing TestEncoderOffsetCalibration(pass_if_ready=True), - TestVelCtrlVsPosCtrl() + TestHighVelocityCtrl(), +# TestVelCtrlVsPosCtrl() # TODO: test step/dir # TODO: test sensorless # TODO: test ASCII protocol From 23009d1e933aed6c22386395c8b9aeaf27f25d84 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 10 Apr 2018 00:25:10 -0700 Subject: [PATCH 055/215] add overvoltage protection The motor phases will go into floating state as soon as an overvoltage condition is detected. --- Firmware/MotorControl/axis.cpp | 9 +++------ Firmware/MotorControl/axis.hpp | 2 -- Firmware/MotorControl/communication.cpp | 4 +++- Firmware/MotorControl/odrive_main.hpp | 5 +++++ 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index c0488dfa..bde389d4 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -92,18 +92,15 @@ void Axis::set_step_dir_enabled(bool enable) { } } -// @brief Returns true if the power supply is within range -bool Axis::check_PSU_brownout() { - return vbus_voltage >= config_.dc_bus_brownout_trip_level; -} - // @brief Returns true if everything is ok. // Sets error and returns false otherwise. bool Axis::do_checks() { if (!motor_.do_checks()) return error_ |= ERROR_MOTOR_FAILED, false; - if (!check_PSU_brownout()) + if (!(vbus_voltage >= board_config.dc_bus_undervoltage_trip_level)) return error_ |= ERROR_DC_BUS_UNDER_VOLTAGE, false; + if (!(vbus_voltage <= board_config.dc_bus_overvoltage_trip_level)) + return error_ |= ERROR_DC_BUS_OVER_VOLTAGE, false; return true; } diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index f6415422..72dac9b3 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -30,7 +30,6 @@ struct AxisConfig_t { // For M0 this has no effect if enable_uart is true float counts_per_step = 2.0f; - float dc_bus_brownout_trip_level = 8.0f; //make_protocol_definitions()), make_protocol_object("axis1", axes[1]->make_protocol_definitions()), diff --git a/Firmware/MotorControl/odrive_main.hpp b/Firmware/MotorControl/odrive_main.hpp index 3ee774f3..43a55a99 100644 --- a/Firmware/MotorControl/odrive_main.hpp +++ b/Firmware/MotorControl/odrive_main.hpp @@ -23,6 +23,11 @@ struct BoardConfig_t { bool enable_uart = true; float brake_resistance = 0.47f; // [ohm] + float dc_bus_undervoltage_trip_level = 8.0f; // Date: Tue, 10 Apr 2018 00:25:56 -0700 Subject: [PATCH 056/215] add high velocity test with load --- tools/odrive/tests.py | 98 ++++++++++++++++++++++++++++++++++--------- tools/run_tests.py | 3 +- 2 files changed, 80 insertions(+), 21 deletions(-) diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index f13c5d71..64237768 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -143,6 +143,8 @@ class AxisTest(ABC): logger.warn("axis still in motion, delaying 2 sec...") time.sleep(2) test_assert_eq(axis_ctx.handle.encoder.pll_vel, 0, range=500) + test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_undervoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 0.92, accuracy=0.001) + test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_overvoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 1.08, accuracy=0.001) @abc.abstractmethod def run_test(self, axis_ctx: AxisTestContext, logger): @@ -222,6 +224,10 @@ class TestSetup(ODriveTest): test_assert_eq(odrv_ctx.handle.config.brake_resistance, 1.0) odrv_ctx.handle.config.brake_resistance = odrv_ctx.yaml['brake-resistance'] test_assert_eq(odrv_ctx.handle.config.brake_resistance, odrv_ctx.yaml['brake-resistance'], accuracy=0.01) + odrv_ctx.handle.config.dc_bus_undervoltage_trip_level = odrv_ctx.yaml['vbus-voltage'] * 0.92 + odrv_ctx.handle.config.dc_bus_overvoltage_trip_level = odrv_ctx.yaml['vbus-voltage'] * 1.08 + test_assert_eq(odrv_ctx.handle.config.dc_bus_undervoltage_trip_level, odrv_ctx.yaml['vbus-voltage'] * 0.92, accuracy=0.001) + test_assert_eq(odrv_ctx.handle.config.dc_bus_overvoltage_trip_level, odrv_ctx.yaml['vbus-voltage'] * 1.08, accuracy=0.001) # firmware has 1500ms startup delay time.sleep(2) @@ -354,7 +360,7 @@ class TestStoreAndReboot(ODriveTest): test_assert_eq(axis_ctx.handle.motor.config.phase_inductance, axis_ctx.yaml['motor-phase-inductance'], accuracy=0.5) -class TestHighVelocityCtrl(AxisTest): +class TestHighVelocity(AxisTest): """ Spins the motor up to it's max speed during a period of 10s. The commanded max speed is based on the motor's KV rating and nominal V_bus, @@ -362,8 +368,18 @@ class TestHighVelocityCtrl(AxisTest): The test passes if the motor follows the commanded ramp closely up to 90% of the theoretical limit (and if no errors occur along the way). """ + def __init__(self, override_current_limit=None, load_current=0, brake=True): + """ + param override_current_limit: If None, the test selects a current limit that is guaranteed + not to fry the brake resistor. If you override the limit, you're + on your own. + """ + self._override_current_limit = override_current_limit + self._load_current = load_current + self._brake = brake + def check_preconditions(self, axis_ctx: AxisTestContext, logger): - super(TestHighVelocityCtrl, self).check_preconditions(axis_ctx, logger) + super(TestHighVelocity, self).check_preconditions(axis_ctx, logger) test_assert_eq(axis_ctx.handle.motor.is_calibrated, True) test_assert_eq(axis_ctx.handle.encoder.is_ready, True) @@ -372,6 +388,7 @@ class TestHighVelocityCtrl(AxisTest): # V_bus and motor KV rating max_rpm = axis_ctx.odrv_ctx.yaml['vbus-voltage'] * axis_ctx.yaml['motor-kv'] rated_limit = max_rpm / 60 * axis_ctx.yaml['encoder-cpr'] + rated_limit *= 0.5 # TODO: remove this later, but for now we want to stay away from the modulation depth limit expected_limit = rated_limit # The KV-rating assumes square-waves on the motor phases (hexagonal space vector trajectory) @@ -390,14 +407,18 @@ class TestHighVelocityCtrl(AxisTest): #theoretical_limit = 100000 # Set the current limit accordingly so we don't burn the brake resistor while slowing down - set_limits(axis_ctx, logger, vel_limit=rated_limit, current_limit=50) + if self._override_current_limit is None: + set_limits(axis_ctx, logger, vel_limit=rated_limit, current_limit=50) + else: + axis_ctx.handle.motor.config.current_lim = self._override_current_limit + axis_ctx.handle.controller.config.vel_limit = rated_limit request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) ramp_up_time = 20.0 t_0 = time.monotonic() last_print = t_0 - max_true_vel = 0.0 + max_measured_vel = 0.0 while True: ratio = (time.monotonic() - t_0) / ramp_up_time if ratio >= 1: @@ -406,29 +427,66 @@ class TestHighVelocityCtrl(AxisTest): # While ramping up we want to remain within +-5% of the setpoint. # However we accept if we can only approach 80% of the theoretical limit. vel_setpoint = ratio * rated_limit - vel_range = max(0.05*vel_setpoint, 5000) - if vel_setpoint - vel_range > expected_limit: - vel_range = vel_setpoint - expected_limit + expected_velocity = max(vel_setpoint - rated_limit / ramp_up_time * self._load_current / 20, 0) + vel_range = max(0.05*expected_velocity, 50000) + if expected_velocity - vel_range > expected_limit: + vel_range = expected_velocity - expected_limit + + # set and measure velocity + axis_ctx.handle.controller.set_vel_setpoint(vel_setpoint, 0) + measured_vel = axis_ctx.handle.encoder.pll_vel + max_measured_vel = max(measured_vel, max_measured_vel) + test_assert_eq(measured_vel, expected_velocity, range=vel_range) + test_assert_no_error(axis_ctx) # log progress if time.monotonic() - last_print > 1: last_print = time.monotonic() - logger.debug("ramping up: now at " + str(vel_setpoint)) - - # set and measure velocity - axis_ctx.handle.controller.set_vel_setpoint(vel_setpoint, 0) - true_vel = axis_ctx.handle.encoder.pll_vel - max_true_vel = max(true_vel, max_true_vel) - test_assert_eq(true_vel, vel_setpoint, range=vel_range) - test_assert_no_error(axis_ctx) + logger.debug("ramping up: commanded {}, expected {}, measured {} ".format(vel_setpoint, expected_velocity, measured_vel)) time.sleep(0.001) - axis_ctx.handle.controller.set_vel_setpoint(0, 0) - time.sleep(0.5) - # TODO: this is not a good bound, but the encoder float resolution results in a bad velocity estimate after this many turns - test_assert_eq(axis_ctx.handle.encoder.pll_vel, 0, range=2000) - request_state(axis_ctx, AXIS_STATE_IDLE) + logger.debug("reached top speed of {} counts/sec".format(max_measured_vel)) + + if self._brake: + axis_ctx.handle.controller.set_vel_setpoint(0, 0) + time.sleep(0.5) + # If the velocity integrator at work, it may now work against slowing down. + test_assert_eq(axis_ctx.handle.encoder.pll_vel, 0, range=rated_limit*0.3) + # TODO: this is not a good bound, but the encoder float resolution results in a bad velocity estimate after this many turns + time.sleep(0.5) + test_assert_eq(axis_ctx.handle.encoder.pll_vel, 0, range=2000) + request_state(axis_ctx, AXIS_STATE_IDLE) + test_assert_no_error(axis_ctx) + + +class TestHighVelocityInViscousFluid(DualAxisTest): + """ + Runs TestHighVelocity on one motor while using the other motor as a load. + The load is created by running velocity control with setpoint 0. + """ + def run_test(self, axis0_ctx: AxisTestContext, axis1_ctx: AxisTestContext, logger): + load_ctx = axis0_ctx + driver_ctx = axis1_ctx + + # Set up viscous fluid load + logger.debug("activating load on {}...".format(load_ctx.name)) + load_ctx.handle.controller.config.vel_integrator_gain = 0 + load_ctx.handle.controller.vel_integrator_current = 0 + load_ctx.handle.controller.config.vel_limit = 20000 # this is not really relevant + load_ctx.handle.motor.config.current_lim = 20 + load_ctx.odrv_ctx.handle.config.brake_resistance = 0 # disable brake resistance, the power will go into the bus + load_ctx.handle.controller.set_vel_setpoint(0, 0) + request_state(load_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) + + driver_test = TestHighVelocity(override_current_limit=40, load_current=20, brake=False) + driver_test.check_preconditions(driver_ctx, logger) + driver_test.run_test(driver_ctx, logger) + + # put load to idle as quickly as possible, otherwise, because the brake resistor is disabled, + # it will try to put the braking power into the power rail where it has nowhere to go. + request_state(load_ctx, AXIS_STATE_IDLE) + request_state(driver_ctx, AXIS_STATE_IDLE) class TestVelCtrlVsPosCtrl(DualAxisTest): diff --git a/tools/run_tests.py b/tools/run_tests.py index f0ee3bb1..14185cf0 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -29,7 +29,8 @@ all_tests = [ # TestClosedLoopControl(), TestDiscoverAndGotoIdle(), # for testing TestEncoderOffsetCalibration(pass_if_ready=True), - TestHighVelocityCtrl(), +# TestHighVelocity(), + TestHighVelocityInViscousFluid(), # TestVelCtrlVsPosCtrl() # TODO: test step/dir # TODO: test sensorless From d202491e9a201355995c7ba48517ae58cf7f66f1 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 10 Apr 2018 15:41:34 -0700 Subject: [PATCH 057/215] make tests more flexible --- docs/testing.md | 23 ++++++++++++++ tools/odrive/tests.py | 17 ++++++++--- tools/run_tests.py | 71 +++++++++++++++++++++++++++---------------- tools/test-rig.yaml | 16 ++++++---- 4 files changed, 90 insertions(+), 37 deletions(-) create mode 100644 docs/testing.md diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 00000000..347db2b1 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,23 @@ +# Automated Testing + +This section describes how to use the automated testing facilities. +You don't have to do this as an end user. + +They test the following aspects: + - System functions (communication interfaces, configuration storage) + - Functionality of the motor controller and state machine + - High speed and high load conditions + +The testing facility consists of the following components: + * **Test rig:** In the simplest case this can be a single ODrive with a single motor and encoder pair. Can also be multiple ODrives with multiple axes, some of which may be mechanically coupled. + * **Test host:** The PC on which the test script runs. All ODrives must be connected to the test host via USB. + * **test-rig.yaml:** Describes your test rig. Make sure all values are correct. Incorrect values may physically break or fry your test setup. + * **run_tests.py:** This is the main script that runs all the tests. + +## How to run + +Example: + +``` +./run_tests.py --skip-boring-tests --ignore top-odrive.yellow bottom-odrive.yellow +``` diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index 64237768..6e4d9cd6 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -26,7 +26,7 @@ class ODriveTestContext(): self.name = name self.axes = [] for axis_idx, axis_yaml in enumerate(yaml['axes']): - axis_name = axis_yaml['name'] if 'name' in axis_yaml else '{}.axis{}'.format(name, axis_idx) + axis_name = (name + "." + axis_yaml['name']) if 'name' in axis_yaml else '{}.axis{}'.format(name, axis_idx) self.axes.append(AxisTestContext(axis_name, axis_yaml, self)) def rediscover(self): @@ -388,7 +388,6 @@ class TestHighVelocity(AxisTest): # V_bus and motor KV rating max_rpm = axis_ctx.odrv_ctx.yaml['vbus-voltage'] * axis_ctx.yaml['motor-kv'] rated_limit = max_rpm / 60 * axis_ctx.yaml['encoder-cpr'] - rated_limit *= 0.5 # TODO: remove this later, but for now we want to stay away from the modulation depth limit expected_limit = rated_limit # The KV-rating assumes square-waves on the motor phases (hexagonal space vector trajectory) @@ -399,6 +398,10 @@ class TestHighVelocity(AxisTest): # The ODrive only goes to 80% modulation depth in order to save some time for the ADC measurements. # See FOC_current in motor.cpp. expected_limit *= 0.8 + + # TODO: remove the following two lines, but for now we want to stay away from the modulation depth limit + expected_limit *= 0.8 + rated_limit = expected_limit # Add a 10% margin to account for expected_limit *= 0.9 @@ -465,6 +468,10 @@ class TestHighVelocityInViscousFluid(DualAxisTest): Runs TestHighVelocity on one motor while using the other motor as a load. The load is created by running velocity control with setpoint 0. """ + def __init__(self, load_current=10, driver_current=20): + self._load_current = load_current + self._driver_current = driver_current + def run_test(self, axis0_ctx: AxisTestContext, axis1_ctx: AxisTestContext, logger): load_ctx = axis0_ctx driver_ctx = axis1_ctx @@ -474,12 +481,14 @@ class TestHighVelocityInViscousFluid(DualAxisTest): load_ctx.handle.controller.config.vel_integrator_gain = 0 load_ctx.handle.controller.vel_integrator_current = 0 load_ctx.handle.controller.config.vel_limit = 20000 # this is not really relevant - load_ctx.handle.motor.config.current_lim = 20 + load_ctx.handle.motor.config.current_lim = self._load_current load_ctx.odrv_ctx.handle.config.brake_resistance = 0 # disable brake resistance, the power will go into the bus load_ctx.handle.controller.set_vel_setpoint(0, 0) request_state(load_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) - driver_test = TestHighVelocity(override_current_limit=40, load_current=20, brake=False) + driver_test = TestHighVelocity( + override_current_limit=self._driver_current, + load_current=self._load_current, brake=False) driver_test.check_preconditions(driver_ctx, logger) driver_test.run_test(driver_ctx, logger) diff --git a/tools/run_tests.py b/tools/run_tests.py index 14185cf0..6931dd49 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -12,52 +12,67 @@ import os import sys import threading import traceback +import argparse from odrive.tests import * from odrive.utils import Logger, for_all_parallel, Event +script_path=os.path.dirname(os.path.realpath(__file__)) -all_tests = [ -# TestFlashAndErase(), -# TestSetup(), -# TestMotorCalibration(), -# # TODO: test encoder index search -# TestEncoderOffsetCalibration(), -# # TODO: hold down one motor while the other one does an index search (should fail) -# TestClosedLoopControl(), -# TestStoreAndReboot(), -# TestEncoderOffsetCalibration(), # need to find offset _or_ index after reboot -# TestClosedLoopControl(), - TestDiscoverAndGotoIdle(), # for testing - TestEncoderOffsetCalibration(pass_if_ready=True), -# TestHighVelocity(), - TestHighVelocityInViscousFluid(), -# TestVelCtrlVsPosCtrl() - # TODO: test step/dir - # TODO: test sensorless - # TODO: test ASCII protocol - # TODO: test protocol over UART -] +parser = argparse.ArgumentParser(description='ODrive automated test tool\n') +parser.add_argument("--skip-boring-tests", action="store_true", + help="Skip the boring tests and go right to the high power tests") +parser.add_argument("--ignore", metavar='DEVICE', action='store', nargs='+', + help="Ignore one or more ODrives or axes") +parser.add_argument("--test-rig-yaml", type=argparse.FileType('r'), + help="test rig YAML file") +parser.set_defaults(test_rig_yaml=script_path + '/test-rig.yaml') +parser.set_defaults(ignore=[]) +args = parser.parse_args() +all_tests = [] +if not args.skip_boring_tests: + all_tests.append(TestFlashAndErase()) + all_tests.append(TestSetup()) + all_tests.append(TestMotorCalibration()) + # # TODO: test encoder index search + all_tests.append(TestEncoderOffsetCalibration()) + # # TODO: hold down one motor while the other one does an index search (should fail) + all_tests.append(TestClosedLoopControl()) + all_tests.append(TestStoreAndReboot()) + all_tests.append(TestEncoderOffsetCalibration()) # need to find offset _or_ index after reboot + all_tests.append(TestClosedLoopControl()) +else: + all_tests.append(TestDiscoverAndGotoIdle()) + all_tests.append(TestEncoderOffsetCalibration(pass_if_ready=True)) +#all_tests.append(TestHighVelocity()) +all_tests.append(TestHighVelocityInViscousFluid(load_current=20, driver_current=40)) +#all_tests.append(TestVelCtrlVsPosCtrl()) +# TODO: test step/dir +# TODO: test sensorless +# TODO: test ASCII protocol +# TODO: test protocol over UART + +print(str(args.ignore)) logger = Logger() -script_path=os.path.dirname(os.path.realpath(__file__)) -with open(script_path + '/test-rig.yaml', 'r') as file_stream: - test_rig_yaml = yaml.load(file_stream) +test_rig_yaml = yaml.load(args.test_rig_yaml) os.chdir(script_path + '/../Firmware') # Build a dictionary of odrive test contexts by name odrives_by_name = {} for odrv_idx, odrv_yaml in enumerate(test_rig_yaml['odrives']): name = odrv_yaml['name'] if 'name' in odrv_yaml else 'odrive{}'.format(odrv_idx) - odrives_by_name[name] = ODriveTestContext(name, odrv_yaml) + if not name in args.ignore: + odrives_by_name[name] = ODriveTestContext(name, odrv_yaml) # Build a dictionary of axis test contexts by name (e.g. odrive0.axis0) axes_by_name = {} for odrv_ctx in odrives_by_name.values(): for axis_idx, axis_ctx in enumerate(odrv_ctx.axes): - axes_by_name[axis_ctx.name] = axis_ctx + if not axis_ctx.name in args.ignore: + axes_by_name[axis_ctx.name] = axis_ctx # Ensure mechanical couplings are valid couplings = [] @@ -65,7 +80,9 @@ if test_rig_yaml['couplings'] is None: test_rig_yaml['couplings'] = {} else: for coupling in test_rig_yaml['couplings']: - couplings.append([axes_by_name[axis_name] for axis_name in coupling]) + c = [axes_by_name[axis_name] for axis_name in coupling if (axis_name in axes_by_name)] + if len(c) > 1: + couplings.append(c) app_shutdown_token = Event() diff --git a/tools/test-rig.yaml b/tools/test-rig.yaml index 2c05cc9d..092bed42 100644 --- a/tools/test-rig.yaml +++ b/tools/test-rig.yaml @@ -11,14 +11,16 @@ odrives: vbus-voltage: 12 # [V] max-brake-power: 150 # [W] axes: - - motor-phase-resistance: 0.0245 + - name: 'yellow' + motor-phase-resistance: 0.0245 motor-phase-inductance: 2.03e-05 motor-pole-pairs: 7 motor-direction: -1 motor-kv: 190 motor-max-current: 50 encoder-cpr: 8192 - - motor-phase-resistance: 0.028 + - name: 'black' + motor-phase-resistance: 0.028 motor-phase-inductance: 1.6e-05 motor-pole-pairs: 7 motor-direction: -1 @@ -35,14 +37,16 @@ odrives: vbus-voltage: 12 # [V] max-brake-power: 150 # [W] axes: - - motor-phase-resistance: 0.0253 + - name: 'black' + motor-phase-resistance: 0.0253 motor-phase-inductance: 1.6e-05 motor-pole-pairs: 7 motor-direction: 1 motor-kv: 270 motor-max-current: 50 encoder-cpr: 8192 - - motor-phase-resistance: 0.0245 + - name: 'yellow' + motor-phase-resistance: 0.0245 motor-phase-inductance: 2.03e-05 motor-pole-pairs: 7 motor-direction: -1 @@ -52,5 +56,5 @@ odrives: # Mechanical couplings couplings: - - [ top-odrive.axis0, bottom-odrive.axis1 ] - - [ top-odrive.axis1, bottom-odrive.axis0 ] + - [ top-odrive.yellow, bottom-odrive.yellow ] + - [ top-odrive.black, bottom-odrive.black ] From a7de97defd5734bc202b27d9cf7ab24b3f47db9e Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 10 Apr 2018 23:45:58 -0700 Subject: [PATCH 058/215] [HACK] implement return values --- Firmware/MotorControl/protocol.hpp | 80 ++++++++++++++++++++++++++++++ tools/odrive/remote_object.py | 7 +++ 2 files changed, 87 insertions(+) diff --git a/Firmware/MotorControl/protocol.hpp b/Firmware/MotorControl/protocol.hpp index bd853608..7c2f9d38 100644 --- a/Firmware/MotorControl/protocol.hpp +++ b/Firmware/MotorControl/protocol.hpp @@ -745,11 +745,91 @@ public: MemberList...> input_properties_; }; +template +class ProtocolFunctionWithRet : Endpoint { +public: + static constexpr size_t endpoint_count = 1 + MemberList>::endpoint_count + MemberList...>::endpoint_count; + template + ProtocolFunctionWithRet(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TArgs...), TNames ... names) : + name_(name), out_arg_names_{"out"}, all_arg_names_{names...}, obj_(obj), func_ptr_(func_ptr), + output_properties_(PropertyListFactory::template make_property_list<0>(out_arg_names_, out_args_)), + input_properties_(PropertyListFactory::template make_property_list<0>(all_arg_names_, in_args_)) + { + LOG_PROTO("my tuple is at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); + } + + ProtocolFunctionWithRet(const ProtocolFunctionWithRet& other) : + name_(other.name_), all_arg_names_(other.all_arg_names_), obj_(other.obj_), func_ptr_(other.func_ptr_), + output_properties_(PropertyListFactory::template make_property_list<0>( + out_arg_names_, out_args_)), + input_properties_(PropertyListFactory::template make_property_list<0>( + all_arg_names_, in_args_)) + { + LOG_PROTO("COPIED! my tuple is at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); + } + + void write_json(size_t id, StreamSink* output) { + // write name + write_string("{\"name\":\"", output); + write_string(name_, output); + + // write endpoint ID + write_string("\",\"id\":", output); + char id_buf[10]; + snprintf(id_buf, sizeof(id_buf), "%u", id); // TODO: get rid of printf + write_string(id_buf, output); + + // write arguments + write_string(",\"type\":\"function\",\"inputs\":[", output); + input_properties_.write_json(id + 1, output), + write_string("],\"outputs\":[", output); + output_properties_.write_json(id + 1 + decltype(input_properties_)::endpoint_count, output), + write_string("]}", output); + } + + void register_endpoints(Endpoint** list, size_t id, size_t length) { + if (id < length) + list[id] = this; + input_properties_.register_endpoints(list, id + 1, length); + output_properties_.register_endpoints(list, id + 1 + decltype(input_properties_)::endpoint_count, length); + } + + void handle(const uint8_t* input, size_t input_length, StreamSink* output) { + (void) input; + (void) input_length; + (void) output; + LOG_PROTO("tuple still at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); + LOG_PROTO("invoke function using %d and %.3f\r\n", std::get<0>(in_args_), std::get<1>(in_args_)); + std::get<0>(out_args_) = invoke_function_with_tuple(obj_, func_ptr_, in_args_); + } + + const char * name_; + std::array out_arg_names_; // TODO: remove + std::array all_arg_names_; // TODO: remove + TObj& obj_; + TRet(TObj::*func_ptr_)(TArgs...); + //TRet ret_val_; + std::tuple out_args_; + std::tuple in_args_; + MemberList> output_properties_; + MemberList...> input_properties_; +}; + +//template> +//ProtocolFunction make_protocol_function(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TArgs...), TNames ... names) { +// return ProtocolFunction(name, obj, func_ptr, names...); +//} + template> ProtocolFunction make_protocol_function(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TArgs...), TNames ... names) { return ProtocolFunction(name, obj, func_ptr, names...); } +template> +ProtocolFunctionWithRet make_protocol_function_with_ret(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TArgs...), TNames ... names) { + return ProtocolFunctionWithRet(name, obj, func_ptr, names...); +} + template diff --git a/tools/odrive/remote_object.py b/tools/odrive/remote_object.py index 58da44ab..bdd69ae2 100644 --- a/tools/odrive/remote_object.py +++ b/tools/odrive/remote_object.py @@ -101,12 +101,19 @@ class RemoteFunction(object): param_json["mode"] = "r" self._inputs.append(RemoteProperty(param_json, parent)) + self._outputs = [] + for param_json in json_data.get("outputs", []): # TODO: deprecate "arguments" keyword + param_json["mode"] = "r" + self._outputs.append(RemoteProperty(param_json, parent)) + def __call__(self, *args): if (len(self._inputs) != len(args)): raise TypeError("expected {} arguments but have {}".format(len(self._inputs), len(args))) for i in range(len(args)): self._inputs[i].set_value(args[i]) self._parent.__channel__.remote_endpoint_operation(self._trigger_id, None, True, 0) + if len(self._outputs) > 0: + return self._outputs[0].get_value() class RemoteObject(object): """ From 1e14c452765d6865af3726a1b9f1e80c4858ac3d Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 10 Apr 2018 23:54:13 -0700 Subject: [PATCH 059/215] [HACK] implement oscilloscope --- Firmware/MotorControl/communication.cpp | 8 ++++++++ Firmware/MotorControl/low_level.cpp | 5 +++++ Firmware/MotorControl/odrive_main.hpp | 4 ++++ tools/odrive/utils.py | 10 ++++++++++ 4 files changed, 27 insertions(+) diff --git a/Firmware/MotorControl/communication.cpp b/Firmware/MotorControl/communication.cpp index 49b4ee35..e7024c58 100644 --- a/Firmware/MotorControl/communication.cpp +++ b/Firmware/MotorControl/communication.cpp @@ -180,6 +180,12 @@ void init_communication(void) { } +float oscilloscope[OSCILLOSCOPE_SIZE] = { + 0.123f, 0.345f, 0.4576f, 1.543f, -50.0f +}; +size_t oscilloscope_pos = 0; + + uint32_t comm_stack_info = 0; // for debugging only // Helper class because the protocol library doesn't yet @@ -191,6 +197,7 @@ public: void erase_configuration_helper() { erase_configuration(); } void NVIC_SystemReset_helper() { NVIC_SystemReset(); } void enter_dfu_mode_helper() { enter_dfu_mode(); } + float get_oscilloscope_val(uint32_t index) { return oscilloscope[index]; } } static_functions; // When adding new functions/variables to the protocol, be careful not to @@ -219,6 +226,7 @@ static inline auto make_obj_tree() { ), make_protocol_object("axis0", axes[0]->make_protocol_definitions()), make_protocol_object("axis1", axes[1]->make_protocol_definitions()), + make_protocol_function_with_ret("get_oscilloscope_val", static_functions, &StaticFunctions::get_oscilloscope_val, "index"), make_protocol_function("save_configuration", static_functions, &StaticFunctions::save_configuration_helper), make_protocol_function("erase_configuration", static_functions, &StaticFunctions::erase_configuration_helper), make_protocol_function("reboot", static_functions, &StaticFunctions::NVIC_SystemReset_helper), diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 98ac5620..185708db 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -322,6 +322,11 @@ void vbus_sense_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { // Only one conversion in sequence, so only rank1 uint32_t ADCValue = HAL_ADCEx_InjectedGetValue(hadc, ADC_INJECTED_RANK_1); vbus_voltage = ADCValue * voltage_scale; + if (axes[0] && !axes[0]->error_ && axes[1] && !axes[1]->error_) { + if (oscilloscope_pos >= OSCILLOSCOPE_SIZE) + oscilloscope_pos = 0; + oscilloscope[oscilloscope_pos++] = vbus_voltage; + } } // This is the callback from the ADC that we expect after the PWM has triggered an ADC conversion. diff --git a/Firmware/MotorControl/odrive_main.hpp b/Firmware/MotorControl/odrive_main.hpp index 43a55a99..3b170736 100644 --- a/Firmware/MotorControl/odrive_main.hpp +++ b/Firmware/MotorControl/odrive_main.hpp @@ -47,6 +47,10 @@ extern bool user_config_loaded_; constexpr size_t AXIS_COUNT = 2; extern Axis *axes[AXIS_COUNT]; +#define OSCILLOSCOPE_SIZE 18000 +extern float oscilloscope[OSCILLOSCOPE_SIZE]; +extern size_t oscilloscope_pos; + // TODO: move // this is technically not thread-safe but practically it might be #define DEFINE_ENUM_FLAG_OPERATORS(ENUMTYPE) \ diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 9852a3f2..db743914 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -88,6 +88,16 @@ def print_drv_regs(name, motor): print("Control Reg 1: " + str(ctrl_reg_1) + " (" + format(ctrl_reg_1, '#013b') + ")") print("Control Reg 2: " + str(ctrl_reg_2) + " (" + format(ctrl_reg_2, '#09b') + ")") +def show_oscilloscope(odrv): + size = 18000 + values = [] + for i in range(size): + values.append(odrv.get_oscilloscope_val(i)) + + import matplotlib.pyplot as plt + plt.plot(values) + plt.show() + def rate_test(device): """ Tests how many integers per second can be transmitted From c49717e38612ae448a78d0d37b3ab3ff0b6e347e Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 11 Apr 2018 00:07:12 -0700 Subject: [PATCH 060/215] improve error output --- tools/odrive/tests.py | 30 +++++++++++++++++++++++++----- tools/run_tests.py | 3 +++ 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index 6e4d9cd6..10814d92 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -54,7 +54,7 @@ def test_assert_eq(observed, expected, range=None, accuracy=None): elif not accuracy is None and ((observed < expected * (1 - accuracy)) or (observed > expected * (1 + accuracy))): raise TestFailed("value out of range: expected {}+-{}% but observed {}".format(expected, accuracy*100.0, observed)) -def test_assert_no_error(axis_ctx: AxisTestContext): +def get_errors(axis_ctx: AxisTestContext): errors = [] if axis_ctx.handle.motor.error != 0: errors.append("motor failed with error {:04X}".format(axis_ctx.handle.motor.error)) @@ -66,6 +66,23 @@ def test_assert_no_error(axis_ctx: AxisTestContext): errors.append("axis failed with error {:04X}".format(axis_ctx.handle.error)) elif len(errors) > 0: errors.append("and by the way: axis reports no error even though there is one") + return errors + +def dump_errors(axis_ctx: AxisTestContext, logger): + errors = get_errors(axis_ctx) + if len(errors): + logger.error("errors on " + axis_ctx.name) + for error in errors: + logger.error(error) + +def clear_errors(axis_ctx: AxisTestContext): + axis_ctx.handle.error = 0 + axis_ctx.handle.encoder.error = 0 + axis_ctx.handle.motor.error = 0 + axis_ctx.handle.sensorless_estimator.error = 0 + +def test_assert_no_error(axis_ctx: AxisTestContext): + errors = get_errors(axis_ctx) if len(errors) > 0: raise TestFailed("\n".join(errors)) @@ -160,8 +177,11 @@ class DualAxisTest(ABC): test_assert_no_error(axis1_ctx) test_assert_eq(axis0_ctx.handle.current_state, AXIS_STATE_IDLE) test_assert_eq(axis1_ctx.handle.current_state, AXIS_STATE_IDLE) - test_assert_eq(axis0_ctx.handle.encoder.pll_vel, 0, range=1000) - test_assert_eq(axis1_ctx.handle.encoder.pll_vel, 0, range=1000) + if (abs(axis0_ctx.handle.encoder.pll_vel) > 500) or (abs(axis1_ctx.handle.encoder.pll_vel) > 500): + logger.warn("some axis still in motion, delaying 2 sec...") + time.sleep(2) + test_assert_eq(axis0_ctx.handle.encoder.pll_vel, 0, range=500) + test_assert_eq(axis1_ctx.handle.encoder.pll_vel, 0, range=500) @abc.abstractmethod def run_test(self, axis0_ctx: AxisTestContext, axis1_ctx: AxisTestContext, logger): @@ -170,8 +190,8 @@ class DualAxisTest(ABC): class TestDiscoverAndGotoIdle(ODriveTest): def run_test(self, odrv_ctx: ODriveTestContext, logger): odrv_ctx.rediscover() - odrv_ctx.axes[0].handle.error = 0 - odrv_ctx.axes[1].handle.error = 0 + clear_errors(odrv_ctx.axes[0]) + clear_errors(odrv_ctx.axes[1]) request_state(odrv_ctx.axes[0], AXIS_STATE_IDLE) request_state(odrv_ctx.axes[1], AXIS_STATE_IDLE) diff --git a/tools/run_tests.py b/tools/run_tests.py index 6931dd49..49a9ff32 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -186,6 +186,9 @@ except: #run("make erase PROGRAMMER='" + odrv_ctx.yaml['programmer'] + "'", logger, timeout=30) odrv_ctx.handle.axis0.requested_state = AXIS_STATE_IDLE odrv_ctx.handle.axis1.requested_state = AXIS_STATE_IDLE + dump_errors(odrv_ctx.axes[0], logger) + dump_errors(odrv_ctx.axes[1], logger) + for_all_parallel(odrives_by_name, lambda x: x['name'], odrv_reset_thread) except: logger.error('///////////////////////////////////////////') From ffff42b4f377089a56d144db895a34330d8e83f4 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 11 Apr 2018 00:07:56 -0700 Subject: [PATCH 061/215] [TEMP] test hacks --- tools/odrive/tests.py | 8 ++++++-- tools/run_tests.py | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index 10814d92..3c0bd0bf 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -160,8 +160,10 @@ class AxisTest(ABC): logger.warn("axis still in motion, delaying 2 sec...") time.sleep(2) test_assert_eq(axis_ctx.handle.encoder.pll_vel, 0, range=500) - test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_undervoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 0.92, accuracy=0.001) - test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_overvoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 1.08, accuracy=0.001) + #test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_undervoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 0.92, accuracy=0.001) + #test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_overvoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 1.08, accuracy=0.001) + #test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_undervoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 0.96, accuracy=0.001) + #test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_overvoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 1.04, accuracy=0.001) @abc.abstractmethod def run_test(self, axis_ctx: AxisTestContext, logger): @@ -495,6 +497,8 @@ class TestHighVelocityInViscousFluid(DualAxisTest): def run_test(self, axis0_ctx: AxisTestContext, axis1_ctx: AxisTestContext, logger): load_ctx = axis0_ctx driver_ctx = axis1_ctx + if load_ctx.name == 'bottom-odrive.black': + odrive.utils.start_liveplotter(lambda: [load_ctx.odrv_ctx.handle.vbus_voltage]) # Set up viscous fluid load logger.debug("activating load on {}...".format(load_ctx.name)) diff --git a/tools/run_tests.py b/tools/run_tests.py index 49a9ff32..cc4c805d 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -29,6 +29,8 @@ parser.set_defaults(test_rig_yaml=script_path + '/test-rig.yaml') parser.set_defaults(ignore=[]) args = parser.parse_args() +# TODO: add --only option + all_tests = [] if not args.skip_boring_tests: all_tests.append(TestFlashAndErase()) From 18260e64c0a2c176e264abc8e9c685dc48a125ab Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 12 Apr 2018 20:41:23 -0700 Subject: [PATCH 062/215] sed backslashing through make --- Firmware/.vscode/c_cpp_properties.json | 16 ++++++++++++---- Firmware/Makefile | 2 +- tools/odrive/tests.py | 4 ++-- tools/test-rig.yaml | 4 ++-- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index 3e925aa9..5042079e 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -26,7 +26,9 @@ "defines": [ "STM32F405xx", "USE_HAL_DRIVER", - "HW_VERSION_MAJOR=3", "HW_VERSION_MINOR=4", "HW_VERSION_VOLTAGE=24", + "HW_VERSION_MAJOR=3", + "HW_VERSION_MINOR=4", + "HW_VERSION_VOLTAGE=24", "__weak=\"__attribute__((weak))\"", "__packed=\"__attribute__((__packed__))\"", "__GNUC__" @@ -38,7 +40,9 @@ "C:/Program Files (x86)/GNU Tools ARM Embedded" ], "limitSymbolsToIncludedHeaders": true - } + }, + "cStandard": "c11", + "cppStandard": "c++14" }, { "name": "Linux", @@ -62,7 +66,9 @@ "defines": [ "STM32F405xx", "USE_HAL_DRIVER", - "HW_VERSION_MAJOR=3", "HW_VERSION_MINOR=4", "HW_VERSION_VOLTAGE=24", + "HW_VERSION_MAJOR=3", + "HW_VERSION_MINOR=4", + "HW_VERSION_VOLTAGE=24", "__weak=\"__attribute__((weak))\"", "__packed=\"__attribute__((__packed__))\"", "__GNUC__" @@ -105,7 +111,9 @@ "defines": [ "STM32F405xx", "USE_HAL_DRIVER", - "HW_VERSION_MAJOR=3", "HW_VERSION_MINOR=4", "HW_VERSION_VOLTAGE=24", + "HW_VERSION_MAJOR=3", + "HW_VERSION_MINOR=4", + "HW_VERSION_VOLTAGE=24", "__weak=\"__attribute__((weak))\"", "__packed=\"__attribute__((__packed__))\"", "__GNUC__" diff --git a/Firmware/Makefile b/Firmware/Makefile index 921a9e13..9f239b22 100644 --- a/Firmware/Makefile +++ b/Firmware/Makefile @@ -5,7 +5,7 @@ BUILD_DIR = build FIRMWARE = $(BUILD_DIR)/ODriveFirmware.elf FIRMWARE_HEX = $(BUILD_DIR)/ODriveFirmware.hex -PROGRAMMER_HEX := $(shell echo $(PROGRAMMER) | sed -e 's/.\{2\}/\\x&/g') +PROGRAMMER_HEX := $(shell echo $(PROGRAMMER) | sed -e 's/.\\{2\\}/\\\\x&/g') OPENOCD := openocd -f interface/stlink-v2.cfg \ $(if $(value PROGRAMMER),-c 'hla_serial $(PROGRAMMER_HEX)',) \ -f target/stm32f4x.cfg diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index 3c0bd0bf..0fc82c1d 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -160,8 +160,8 @@ class AxisTest(ABC): logger.warn("axis still in motion, delaying 2 sec...") time.sleep(2) test_assert_eq(axis_ctx.handle.encoder.pll_vel, 0, range=500) - #test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_undervoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 0.92, accuracy=0.001) - #test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_overvoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 1.08, accuracy=0.001) + test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_undervoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 0.92, accuracy=0.001) + test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_overvoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 1.08, accuracy=0.001) #test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_undervoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 0.96, accuracy=0.001) #test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_overvoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 1.04, accuracy=0.001) diff --git a/tools/test-rig.yaml b/tools/test-rig.yaml index 092bed42..3053070f 100644 --- a/tools/test-rig.yaml +++ b/tools/test-rig.yaml @@ -8,7 +8,7 @@ odrives: uart: /dev/serial/by-id/[not-yet-used] usb: auto programmer: '533f7506493f49514454193f' - vbus-voltage: 12 # [V] + vbus-voltage: 24 # [V] max-brake-power: 150 # [W] axes: - name: 'yellow' @@ -34,7 +34,7 @@ odrives: uart: /dev/serial/by-id/[not-yet-used] usb: auto programmer: '493f6f06493f56540929113f' - vbus-voltage: 12 # [V] + vbus-voltage: 24 # [V] max-brake-power: 150 # [W] axes: - name: 'black' From 2f5f6ead9c8631958255275fc44bedf9afd1172d Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 13 Apr 2018 00:25:03 -0700 Subject: [PATCH 063/215] add retry error counter, some fixes for windows --- tools/odrive/protocol.py | 7 ++++++- tools/odrive/tests.py | 9 +++++++-- tools/odrive/usbbulk_transport.py | 4 ++-- tools/odrive/utils.py | 6 ++++++ tools/odrivetool | 6 +----- tools/run_tests.py | 10 +++++----- tools/test-rig.yaml | 4 ++-- 7 files changed, 29 insertions(+), 17 deletions(-) diff --git a/tools/odrive/protocol.py b/tools/odrive/protocol.py index f4bf2b05..a99bc0b1 100644 --- a/tools/odrive/protocol.py +++ b/tools/odrive/protocol.py @@ -232,8 +232,10 @@ class Channel(PacketSink): The thread quits as soon as the channel enters a broken state. """ def receiver_thread(): + error_ctr = 0 try: - while (not cancellation_token.is_set()) and (not self._channel_broken.is_set()): + while (not cancellation_token.is_set() and not self._channel_broken.is_set() + and error_ctr < 10): # Set an arbitrary deadline because the get_packet function # currently doesn't support a cancellation_token deadline = time.monotonic() + 1.0 @@ -242,7 +244,10 @@ class Channel(PacketSink): except odrive.utils.TimeoutException: continue # try again except ChannelDamagedException: + error_ctr += 1 continue # try again + if (error_ctr > 0): + error_ctr -= 1 # Process response # This should not throw an exception, otherwise the channel breaks self.process_packet(response) diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index 0fc82c1d..44205006 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -9,6 +9,9 @@ import odrive.discovery from odrive.enums import * import odrive.utils +import functools +print = functools.partial(print, flush=True) + import abc ABC = abc.ABC @@ -34,7 +37,7 @@ class ODriveTestContext(): Reconnects to the ODrive """ self.handle = odrive.discovery.find_any( - path="usb", serial_number=self.yaml['serial-number'], timeout=15) + path="usb", serial_number=self.yaml['serial-number'], timeout=15)#, printer=print) for axis_idx, axis_ctx in enumerate(self.axes): axis_ctx.handle = self.handle.__dict__['axis{}'.format(axis_idx)] @@ -378,7 +381,7 @@ class TestStoreAndReboot(ODriveTest): test_assert_eq(odrv_ctx.handle.config.brake_resistance, odrv_ctx.yaml['brake-resistance'], accuracy=0.01) for axis_ctx in odrv_ctx.axes: test_assert_eq(axis_ctx.handle.encoder.config.cpr, axis_ctx.yaml['encoder-cpr']) - test_assert_eq(axis_ctx.handle.motor.config.phase_resistance, axis_ctx.yaml['motor-phase-resistance'], accuracy=0.15) + test_assert_eq(axis_ctx.handle.motor.config.phase_resistance, axis_ctx.yaml['motor-phase-resistance'], accuracy=0.2) test_assert_eq(axis_ctx.handle.motor.config.phase_inductance, axis_ctx.yaml['motor-phase-inductance'], accuracy=0.5) @@ -437,6 +440,8 @@ class TestHighVelocity(AxisTest): else: axis_ctx.handle.motor.config.current_lim = self._override_current_limit axis_ctx.handle.controller.config.vel_limit = rated_limit + axis_ctx.handle.controller.vel_integrator_current = 0 + axis_ctx.handle.controller.set_vel_setpoint(0, 0) request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) diff --git a/tools/odrive/usbbulk_transport.py b/tools/odrive/usbbulk_transport.py index fbfcfcee..dc36f334 100644 --- a/tools/odrive/usbbulk_transport.py +++ b/tools/odrive/usbbulk_transport.py @@ -118,7 +118,7 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) except usb.core.USBError as ex: if ex.errno == 19: # "no such device" raise odrive.protocol.ChannelBrokenException() - elif ex.errno == 110: # timeout + elif ex.errno is None or ex.errno == 110: # timeout raise odrive.utils.TimeoutException() else: self._printer("halt condition: {}".format(ex.errno)) @@ -172,7 +172,7 @@ def discover_channels(path, serial_number, callback, cancellation_token, printer return True while not cancellation_token.is_set(): - printer("USB discover loop") + # printer("USB discover loop") devices = usb.core.find(find_all=True, custom_match=device_matcher) for usb_device in devices: try: diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index db743914..6b20cbd3 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -67,6 +67,7 @@ def start_liveplotter(get_var_callback): plt.plot(vals) #time.sleep(1/plot_rate) fig.canvas.flush_events() + plt.pause(1/plot_rate) threading.Thread(target=fetch_data).start() threading.Thread(target=plot_data).start() @@ -303,6 +304,7 @@ class Logger(): self._prefix = '' self._skip_bottom_line = False # If true, messages are printed one line above the cursor self._verbose = verbose + self._print_lock = threading.Lock() if platform.system() == 'Windows': self._stdout_buf = win32console.GetStdHandle(win32console.STD_OUTPUT_HANDLE) @@ -350,18 +352,22 @@ class Logger(): # (print text) # ESC 8: restore old cursor position + self._print_lock.acquire() sys.stdout.write('\x1b7\x1b[1A\x1b[1S\x1b[1L') sys.stdout.write(Logger._VT100Colors[color] + text + Logger._VT100Colors[Logger.COLOR_DEFAULT]) sys.stdout.write('\x1b8') sys.stdout.flush() + self._print_lock.release() def print_colored(self, text, color): if self._skip_bottom_line: self.print_on_second_last_line(text, color) else: # On Windows, colorama does the job of interpreting the VT100 escape sequences + self._print_lock.acquire() sys.stdout.write(Logger._VT100Colors[color] + text + Logger._VT100Colors[Logger.COLOR_DEFAULT] + '\n') sys.stdout.flush() + self._print_lock.release() def debug(self, text): if self._verbose: diff --git a/tools/odrivetool b/tools/odrivetool index 0bcc39b7..4afa2927 100755 --- a/tools/odrivetool +++ b/tools/odrivetool @@ -7,7 +7,7 @@ import argparse import odrive.discovery from odrive.utils import Logger, Event -# Flush stdout by default +# We are interactively printing status messages, so flush by default import functools print = functools.partial(print, flush=True) @@ -68,10 +68,6 @@ if args.command is None: args.command = 'shell' args.no_ipython = False -# We are interactively printing status messages, so flush by default -import functools -print = functools.partial(print, flush=True) - # TODO: deprecate printer - use logger instead if (args.verbose): printer = print diff --git a/tools/run_tests.py b/tools/run_tests.py index cc4c805d..4142dc3c 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -93,7 +93,7 @@ try: if isinstance(test, ODriveTest): def odrv_test_thread(odrv_name): odrv_ctx = odrives_by_name[odrv_name] - logger.info('● running {} on {}...'.format(type(test).__name__, odrv_name)) + logger.info('* running {} on {}...'.format(type(test).__name__, odrv_name)) try: test.check_preconditions(odrv_ctx, logger.indent(' {}: '.format(odrv_name))) @@ -122,7 +122,7 @@ try: try: if not app_shutdown_token.is_set(): # Run test on this axis - logger.info('● running {} on {}...'.format(type(test).__name__, axis_name)) + logger.info('* running {} on {}...'.format(type(test).__name__, axis_name)) try: test.check_preconditions(axis_ctx, logger.indent(' {}: '.format(axis_name))) @@ -131,7 +131,7 @@ try: test.run_test(axis_ctx, logger.indent(' {}: '.format(axis_name))) else: - logger.warn('⬛ skipping {} on {}'.format(type(test).__name__, axis_name)) + logger.warn('- skipping {} on {}'.format(type(test).__name__, axis_name)) except: app_shutdown_token.set() raise @@ -154,7 +154,7 @@ try: try: if not app_shutdown_token.is_set(): # Run test on this axis - logger.info('● running {} on {}...'.format(type(test).__name__, coupling_name)) + logger.info('* running {} on {}...'.format(type(test).__name__, coupling_name)) try: test.check_preconditions(coupled_axes[0], coupled_axes[1], logger.indent(' {}: '.format(coupling_name))) @@ -163,7 +163,7 @@ try: test.run_test(coupled_axes[0], coupled_axes[1], logger.indent(' {}: '.format(coupling_name))) else: - logger.warn('⬛ skipping {} on {}...'.format(type(test).__name__, coupling_name)) + logger.warn('- skipping {} on {}...'.format(type(test).__name__, coupling_name)) except: app_shutdown_token.set() raise diff --git a/tools/test-rig.yaml b/tools/test-rig.yaml index 3053070f..7e84dec8 100644 --- a/tools/test-rig.yaml +++ b/tools/test-rig.yaml @@ -15,7 +15,7 @@ odrives: motor-phase-resistance: 0.0245 motor-phase-inductance: 2.03e-05 motor-pole-pairs: 7 - motor-direction: -1 + motor-direction: 1 motor-kv: 190 motor-max-current: 50 encoder-cpr: 8192 @@ -38,7 +38,7 @@ odrives: max-brake-power: 150 # [W] axes: - name: 'black' - motor-phase-resistance: 0.0253 + motor-phase-resistance: 0.028 motor-phase-inductance: 1.6e-05 motor-pole-pairs: 7 motor-direction: 1 From 687fdabbc086375c59fe28bdc8f49e2a14c66de0 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 13 Apr 2018 17:17:24 -0700 Subject: [PATCH 064/215] split plotting behaviour across windows and notwindows --- tools/odrive/utils.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 6b20cbd3..af54cf3c 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -65,9 +65,10 @@ def start_liveplotter(get_var_callback): while not cancellation_token.is_set(): plt.clf() plt.plot(vals) - #time.sleep(1/plot_rate) - fig.canvas.flush_events() - plt.pause(1/plot_rate) + if platform.system() == "Windows": + plt.pause(1/plot_rate) + else: + fig.canvas.flush_events() threading.Thread(target=fetch_data).start() threading.Thread(target=plot_data).start() From d864730ecc5927396e8eb10a0b3d8226794b03ea Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 13 Apr 2018 18:37:39 -0700 Subject: [PATCH 065/215] add integrator resets to firmware --- Firmware/MotorControl/controller.cpp | 7 +++++++ Firmware/MotorControl/controller.hpp | 1 + Firmware/MotorControl/motor.cpp | 16 ++++++++++++---- Firmware/MotorControl/motor.hpp | 2 ++ tools/odrive/tests.py | 13 +++++++++++-- 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 3e1b5661..b433efe7 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -6,6 +6,13 @@ Controller::Controller(ControllerConfig_t& config) : config_(config) {} +void Controller::reset() { + pos_setpoint_ = 0.0f; + vel_setpoint_ = 0.0f; + vel_integrator_current_ = 0.0f; + current_setpoint_ = 0.0f; +} + //-------------------------------- // Command Handling //-------------------------------- diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index b5767b6f..3a495d18 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -26,6 +26,7 @@ struct ControllerConfig_t { class Controller { public: Controller(ControllerConfig_t& config); + void reset(); void set_pos_setpoint(float pos_setpoint, float vel_feed_forward, float current_feed_forward); void set_vel_setpoint(float vel_setpoint, float current_feed_forward); diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 0229e697..6f12474d 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -34,17 +34,25 @@ Motor::Motor(const MotorHardwareConfig_t& hw_config, // // @returns: True on success, false otherwise bool Motor::arm() { - // Wait until the interrupt handler triggers twice. After the first wait there is an - // undefined period until the next trigger. After the second wait we know for sure - // that we have exactly one full interrupt period until the third trigger. This gives + + // Reset controller states, integrators, setpoints, etc. + axis_->controller_.reset(); + reset_current_control(); + + // Wait until the interrupt handler triggers twice. This gives // the control loop the correct time quota to set up modulation timings. - if (!(axis_->wait_for_current_meas() && axis_->wait_for_current_meas())) + if (!axis_->wait_for_current_meas()) return axis_->error_ |= Axis::ERROR_CURRENT_MEASUREMENT_TIMEOUT, false; next_timings_valid_ = false; safety_critical_arm_motor_pwm(*this); return true; } +void Motor::reset_current_control() { + current_control_.v_current_control_integral_d = 0.0f; + current_control_.v_current_control_integral_q = 0.0f; +} + // @brief Tune the current controller based on phase resistance and inductance // This should be invoked whenever one of these values changes. // TODO: allow update on user-request or update automatically via hooks diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index b2a79f88..ebc47b9c 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -95,6 +95,8 @@ public: update_current_controller_gains(); DRV8301_setup(); } + void reset_current_control(); + void update_current_controller_gains(); void DRV8301_setup(); bool check_DRV_fault(); diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index 44205006..4a28b449 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -425,7 +425,7 @@ class TestHighVelocity(AxisTest): expected_limit *= 0.8 # TODO: remove the following two lines, but for now we want to stay away from the modulation depth limit - expected_limit *= 0.8 + expected_limit *= 0.5 rated_limit = expected_limit # Add a 10% margin to account for @@ -441,11 +441,15 @@ class TestHighVelocity(AxisTest): axis_ctx.handle.motor.config.current_lim = self._override_current_limit axis_ctx.handle.controller.config.vel_limit = rated_limit axis_ctx.handle.controller.vel_integrator_current = 0 + # logger.debug("Setting {} integrator current to 0".format(axis_ctx.name)) axis_ctx.handle.controller.set_vel_setpoint(0, 0) + # logger.debug("Setting {} vel setpoint to 0".format(axis_ctx.name)) + axis_ctx.handle.motor.current_control.v_current_control_integral_d = 0 + axis_ctx.handle.motor.current_control.v_current_control_integral_q = 0 request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) - ramp_up_time = 20.0 + ramp_up_time = 10.0 t_0 = time.monotonic() last_print = t_0 max_measured_vel = 0.0 @@ -509,10 +513,15 @@ class TestHighVelocityInViscousFluid(DualAxisTest): logger.debug("activating load on {}...".format(load_ctx.name)) load_ctx.handle.controller.config.vel_integrator_gain = 0 load_ctx.handle.controller.vel_integrator_current = 0 + # logger.debug("Setting {} integrator current to 0".format(load_ctx.name)) load_ctx.handle.controller.config.vel_limit = 20000 # this is not really relevant load_ctx.handle.motor.config.current_lim = self._load_current load_ctx.odrv_ctx.handle.config.brake_resistance = 0 # disable brake resistance, the power will go into the bus load_ctx.handle.controller.set_vel_setpoint(0, 0) + # logger.debug("Setting {} vel setpoint to 0".format(load_ctx.name)) + load_ctx.handle.motor.current_control.v_current_control_integral_d = 0 + load_ctx.handle.motor.current_control.v_current_control_integral_q = 0 + request_state(load_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) driver_test = TestHighVelocity( From 99e6334089d07762072ae7dcd75dc4ef90e3b813 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 14 Apr 2018 20:28:31 -0700 Subject: [PATCH 066/215] make rotor still check pause more further from error, reduce undervoltage trip level --- tools/odrive/tests.py | 43 +++++++++++++++++++------------------------ tools/run_tests.py | 4 ++-- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index 4a28b449..eb052947 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -159,11 +159,11 @@ class AxisTest(ABC): def check_preconditions(self, axis_ctx: AxisTestContext, logger): test_assert_no_error(axis_ctx) test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) - if (abs(axis_ctx.handle.encoder.pll_vel) > 500): + if (abs(axis_ctx.handle.encoder.pll_vel) > 100): logger.warn("axis still in motion, delaying 2 sec...") time.sleep(2) test_assert_eq(axis_ctx.handle.encoder.pll_vel, 0, range=500) - test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_undervoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 0.92, accuracy=0.001) + test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_undervoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 0.85, accuracy=0.001) test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_overvoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 1.08, accuracy=0.001) #test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_undervoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 0.96, accuracy=0.001) #test_assert_eq(axis_ctx.odrv_ctx.handle.config.dc_bus_overvoltage_trip_level, axis_ctx.odrv_ctx.yaml['vbus-voltage'] * 1.04, accuracy=0.001) @@ -182,7 +182,7 @@ class DualAxisTest(ABC): test_assert_no_error(axis1_ctx) test_assert_eq(axis0_ctx.handle.current_state, AXIS_STATE_IDLE) test_assert_eq(axis1_ctx.handle.current_state, AXIS_STATE_IDLE) - if (abs(axis0_ctx.handle.encoder.pll_vel) > 500) or (abs(axis1_ctx.handle.encoder.pll_vel) > 500): + if (abs(axis0_ctx.handle.encoder.pll_vel) > 100) or (abs(axis1_ctx.handle.encoder.pll_vel) > 100): logger.warn("some axis still in motion, delaying 2 sec...") time.sleep(2) test_assert_eq(axis0_ctx.handle.encoder.pll_vel, 0, range=500) @@ -249,9 +249,9 @@ class TestSetup(ODriveTest): test_assert_eq(odrv_ctx.handle.config.brake_resistance, 1.0) odrv_ctx.handle.config.brake_resistance = odrv_ctx.yaml['brake-resistance'] test_assert_eq(odrv_ctx.handle.config.brake_resistance, odrv_ctx.yaml['brake-resistance'], accuracy=0.01) - odrv_ctx.handle.config.dc_bus_undervoltage_trip_level = odrv_ctx.yaml['vbus-voltage'] * 0.92 + odrv_ctx.handle.config.dc_bus_undervoltage_trip_level = odrv_ctx.yaml['vbus-voltage'] * 0.85 odrv_ctx.handle.config.dc_bus_overvoltage_trip_level = odrv_ctx.yaml['vbus-voltage'] * 1.08 - test_assert_eq(odrv_ctx.handle.config.dc_bus_undervoltage_trip_level, odrv_ctx.yaml['vbus-voltage'] * 0.92, accuracy=0.001) + test_assert_eq(odrv_ctx.handle.config.dc_bus_undervoltage_trip_level, odrv_ctx.yaml['vbus-voltage'] * 0.85, accuracy=0.001) test_assert_eq(odrv_ctx.handle.config.dc_bus_overvoltage_trip_level, odrv_ctx.yaml['vbus-voltage'] * 1.08, accuracy=0.001) # firmware has 1500ms startup delay @@ -425,7 +425,7 @@ class TestHighVelocity(AxisTest): expected_limit *= 0.8 # TODO: remove the following two lines, but for now we want to stay away from the modulation depth limit - expected_limit *= 0.5 + expected_limit *= 0.6 rated_limit = expected_limit # Add a 10% margin to account for @@ -440,29 +440,29 @@ class TestHighVelocity(AxisTest): else: axis_ctx.handle.motor.config.current_lim = self._override_current_limit axis_ctx.handle.controller.config.vel_limit = rated_limit - axis_ctx.handle.controller.vel_integrator_current = 0 - # logger.debug("Setting {} integrator current to 0".format(axis_ctx.name)) - axis_ctx.handle.controller.set_vel_setpoint(0, 0) - # logger.debug("Setting {} vel setpoint to 0".format(axis_ctx.name)) - axis_ctx.handle.motor.current_control.v_current_control_integral_d = 0 - axis_ctx.handle.motor.current_control.v_current_control_integral_q = 0 request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) + logger.debug("Drive current {}A, Load current {}A".format(axis_ctx.handle.motor.config.current_lim, self._load_current)) - ramp_up_time = 10.0 + ramp_up_time = 15.0 t_0 = time.monotonic() last_print = t_0 max_measured_vel = 0.0 + logger.debug("ramping to {} over {} s".format(rated_limit, ramp_up_time)) while True: ratio = (time.monotonic() - t_0) / ramp_up_time if ratio >= 1: break + #TODO based on integrator gain and torque ramp rate + expected_ramp_lag = 1.0 * (rated_limit / ramp_up_time) + expected_lag = 0 + # While ramping up we want to remain within +-5% of the setpoint. # However we accept if we can only approach 80% of the theoretical limit. vel_setpoint = ratio * rated_limit - expected_velocity = max(vel_setpoint - rated_limit / ramp_up_time * self._load_current / 20, 0) - vel_range = max(0.05*expected_velocity, 50000) + expected_velocity = max(vel_setpoint - expected_lag, 0) + vel_range = max(0.05*expected_velocity, max(expected_lag+expected_ramp_lag, 2000)) if expected_velocity - vel_range > expected_limit: vel_range = expected_velocity - expected_limit @@ -506,21 +506,17 @@ class TestHighVelocityInViscousFluid(DualAxisTest): def run_test(self, axis0_ctx: AxisTestContext, axis1_ctx: AxisTestContext, logger): load_ctx = axis0_ctx driver_ctx = axis1_ctx - if load_ctx.name == 'bottom-odrive.black': - odrive.utils.start_liveplotter(lambda: [load_ctx.odrv_ctx.handle.vbus_voltage]) + if driver_ctx.name == 'top-odrive.black': + # odrive.utils.start_liveplotter(lambda: [driver_ctx.odrv_ctx.handle.vbus_voltage]) + odrive.utils.start_liveplotter(lambda: [driver_ctx.handle.motor.current_control.Iq_measured, + driver_ctx.handle.motor.current_control.Iq_setpoint]) # Set up viscous fluid load logger.debug("activating load on {}...".format(load_ctx.name)) load_ctx.handle.controller.config.vel_integrator_gain = 0 - load_ctx.handle.controller.vel_integrator_current = 0 - # logger.debug("Setting {} integrator current to 0".format(load_ctx.name)) load_ctx.handle.controller.config.vel_limit = 20000 # this is not really relevant load_ctx.handle.motor.config.current_lim = self._load_current load_ctx.odrv_ctx.handle.config.brake_resistance = 0 # disable brake resistance, the power will go into the bus - load_ctx.handle.controller.set_vel_setpoint(0, 0) - # logger.debug("Setting {} vel setpoint to 0".format(load_ctx.name)) - load_ctx.handle.motor.current_control.v_current_control_integral_d = 0 - load_ctx.handle.motor.current_control.v_current_control_integral_q = 0 request_state(load_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) @@ -535,7 +531,6 @@ class TestHighVelocityInViscousFluid(DualAxisTest): request_state(load_ctx, AXIS_STATE_IDLE) request_state(driver_ctx, AXIS_STATE_IDLE) - class TestVelCtrlVsPosCtrl(DualAxisTest): """ Uses one ODrive as a load operating in velocity control mode. diff --git a/tools/run_tests.py b/tools/run_tests.py index 4142dc3c..dc897fde 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -48,8 +48,8 @@ else: all_tests.append(TestEncoderOffsetCalibration(pass_if_ready=True)) #all_tests.append(TestHighVelocity()) -all_tests.append(TestHighVelocityInViscousFluid(load_current=20, driver_current=40)) -#all_tests.append(TestVelCtrlVsPosCtrl()) +all_tests.append(TestHighVelocityInViscousFluid(load_current=60, driver_current=70)) +# all_tests.append(TestVelCtrlVsPosCtrl()) # TODO: test step/dir # TODO: test sensorless # TODO: test ASCII protocol From 339fe59f3011bf1915d951d7d005693fff747365 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 14 Apr 2018 22:47:52 -0700 Subject: [PATCH 067/215] make pos estimate available --- Firmware/MotorControl/axis.hpp | 8 ++++++-- Firmware/MotorControl/encoder.cpp | 21 ++++++++++++--------- Firmware/MotorControl/encoder.hpp | 8 ++++++-- Firmware/MotorControl/utils.c | 7 ------- Firmware/MotorControl/utils.h | 14 ++++++++++++-- 5 files changed, 36 insertions(+), 22 deletions(-) diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 72dac9b3..e7833202 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -119,8 +119,9 @@ public: if (!do_checks()) // error set during function call break; - if (!update_handler()) // error set during function call - break; + // Run main loop function, defer quitting for after wait + // TODO: change arming logic to arm after waiting + bool main_continue = update_handler(); // Check we meet deadlines after queueing ++loop_counter_; @@ -134,6 +135,9 @@ public: error_ |= ERROR_CURRENT_MEASUREMENT_TIMEOUT; break; } + + if (!main_continue) + break; } } diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index fad27267..b5ae480e 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -51,8 +51,8 @@ void Encoder::set_count(int32_t count) { uint32_t prim = __get_PRIMASK(); __disable_irq(); // Offset and state must be shifted by the same amount - offset_ += count - state_; - state_ = count; + offset_ += count - shadow_count_; + shadow_count_ = count; //TODO FIXME hw_config_.timer->Instance->CNT = count; pll_pos_ = (float)count; __set_PRIMASK(prim); @@ -197,25 +197,28 @@ bool Encoder::update(float* pos_estimate, float* vel_estimate, float* phase_outp } // update internal encoder state - int16_t delta_enc = (int16_t)hw_config_.timer->Instance->CNT - (int16_t)state_; - state_ += (int32_t)delta_enc; + int16_t delta_enc_16 = (int16_t)hw_config_.timer->Instance->CNT - (int16_t)shadow_count_; + int32_t delta_enc = (int32_t)delta_enc_16; //sign extend + shadow_count_ += delta_enc; + count_in_cpr_ += delta_enc; + count_in_cpr_ = mod(count_in_cpr_, config_.cpr); // compute electrical phase - int corrected_enc = state_ % config_.cpr; - corrected_enc -= offset_; - //corrected_enc *= axis_->motor_.config_.direction; TODO: verify if this still works + int corrected_enc = count_in_cpr_ - offset_; //TODO avoid recomputing elec_rad_per_enc every time float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / (float)(config_.cpr)); float ph = elec_rad_per_enc * (float)corrected_enc; // ph = fmodf(ph, 2*M_PI); phase_ = wrap_pm_pi(ph); + // run pll (for now pll is in units of encoder counts) - // TODO pll_pos runs out of precision very quickly here! Perhaps decompose into integer and fractional part? // Predict current pos + pos_estimate_ += current_meas_period * pll_vel_; pll_pos_ += current_meas_period * pll_vel_; // discrete phase detector - float delta_pos = (float)(state_ - (int32_t)floorf(pll_pos_)); + // float delta_pos = (float)(shadow_count_ - (int32_t)floorf(pll_pos_)); + float delta_pos = (float)(shadow_count_ - (int32_t)floorf(pll_pos_)); // pll feedback pll_pos_ += current_meas_period * pll_kp_ * delta_pos; pll_vel_ += current_meas_period * pll_ki_ * delta_pos; diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index ae4b3c0e..57194e75 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -50,9 +50,11 @@ public: Error_t error_ = ERROR_NONE; bool index_found_ = false; bool is_ready_ = false; - int32_t state_ = 0; + int32_t shadow_count_ = 0; + int32_t count_in_cpr_ = 0; int32_t offset_ = 0; float phase_ = 0.0f; // [rad] + float pos_estimate_ = 0.0f; // [rad] float pll_pos_ = 0.0f; // [rad] float pll_vel_ = 0.0f; // [rad/s] float pll_kp_ = 0.0f; // [rad/s / rad] @@ -64,9 +66,11 @@ public: make_protocol_property("error", &error_), make_protocol_ro_property("is_ready", &is_ready_), make_protocol_ro_property("index_found", const_cast(&index_found_)), - make_protocol_property("state", &state_), + make_protocol_property("shadow_count", &shadow_count_), + make_protocol_property("count_in_cpr", &count_in_cpr_), make_protocol_property("offset", &offset_), make_protocol_property("phase", &phase_), + make_protocol_property("pos_estimate", &pos_estimate_), make_protocol_property("pll_pos", &pll_pos_), make_protocol_property("pll_vel", &pll_vel_), make_protocol_property("pll_kp", &pll_kp_), diff --git a/Firmware/MotorControl/utils.c b/Firmware/MotorControl/utils.c index 0c80088e..4fa8c378 100644 --- a/Firmware/MotorControl/utils.c +++ b/Firmware/MotorControl/utils.c @@ -128,13 +128,6 @@ int SVM(float alpha, float beta, float* tA, float* tB, float* tC) { return result_valid ? 0 : -1; } -//beware of inserting large angles! -float wrap_pm_pi(float theta) { - while (theta >= M_PI) theta -= (2.0f * M_PI); - while (theta < -M_PI) theta += (2.0f * M_PI); - return theta; -} - // based on https://math.stackexchange.com/a/1105038/81278 float fast_atan2(float y, float x) { // a := min (|x|, |y|) / max (|x|, |y|) diff --git a/Firmware/MotorControl/utils.h b/Firmware/MotorControl/utils.h index a4d7a6e0..158065a0 100644 --- a/Firmware/MotorControl/utils.h +++ b/Firmware/MotorControl/utils.h @@ -82,14 +82,24 @@ static const float one_by_sqrt3 = 0.57735026919f; static const float two_by_sqrt3 = 1.15470053838f; static const float sqrt3_by_2 = 0.86602540378f; +//beware of inserting large values! +static inline float wrap_pm(float x, float pm_range) { + while (x >= pm_range) x -= (2.0f * pm_range); + while (x < -pm_range) x += (2.0f * pm_range); + return x; +} + +//beware of inserting large angles! +static inline float wrap_pm_pi(float theta) { + return wrap_pm(theta, M_PI); +} + // Compute rising edge timings (0.0 - 1.0) as a function of alpha-beta // as per the magnitude invariant clarke transform // The magnitude of the alpha-beta vector may not be larger than sqrt(3)/2 // Returns 0 on success, and -1 if the input was out of range int SVM(float alpha, float beta, float* tA, float* tB, float* tC); -//beware of inserting large angles! -float wrap_pm_pi(float theta); float fast_atan2(float y, float x); int mod(int dividend, int divisor); From e4b199e5c6b69fe99032cf96dd5fa346a1e99388 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 14 Apr 2018 23:43:21 -0700 Subject: [PATCH 068/215] some nan checks require overriding fast math --- Firmware/Tupfile.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index 4902285b..6aeb6590 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -107,7 +107,7 @@ LDFLAGS += '-Wl,--undefined=uxTopUsedPriority' -- common flags for ASM, C and C++ OPT += '-Og' -OPT += '-ffast-math' +OPT += '-ffast-math -fno-finite-math-only' tup.append_table(FLAGS, OPT) tup.append_table(LDFLAGS, OPT) From 0b1bf6f278413fe14e4c7dc4ac6795e198f34361 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sun, 15 Apr 2018 00:38:39 -0700 Subject: [PATCH 069/215] implement circular vel pll tracking --- Firmware/MotorControl/encoder.cpp | 28 ++++++++++++++++++++-------- Firmware/MotorControl/encoder.hpp | 4 ++-- Firmware/MotorControl/utils.h | 9 +++++++++ tools/odrive/shell.py | 2 +- tools/odrive/tests.py | 12 ++++++------ tools/odrivetool | 4 ++-- 6 files changed, 40 insertions(+), 19 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index b5ae480e..7f573752 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -50,11 +50,20 @@ void Encoder::set_count(int32_t count) { // Disable interrupts to make a critical section to avoid race condition uint32_t prim = __get_PRIMASK(); __disable_irq(); + + // Update states + shadow_count_ = count; + pos_estimate_ = (float)count; + count_in_cpr_ = mod(count, config_.cpr); + pos_cpr = (float)count_in_cpr_; + // Offset and state must be shifted by the same amount offset_ += count - shadow_count_; - shadow_count_ = count; //TODO FIXME + offset_ = mod(offset_, config_.cpr); + + //Write hardware last hw_config_.timer->Instance->CNT = count; - pll_pos_ = (float)count; + __set_PRIMASK(prim); } @@ -215,16 +224,19 @@ bool Encoder::update(float* pos_estimate, float* vel_estimate, float* phase_outp // run pll (for now pll is in units of encoder counts) // Predict current pos pos_estimate_ += current_meas_period * pll_vel_; - pll_pos_ += current_meas_period * pll_vel_; + pos_cpr += current_meas_period * pll_vel_; // discrete phase detector - // float delta_pos = (float)(shadow_count_ - (int32_t)floorf(pll_pos_)); - float delta_pos = (float)(shadow_count_ - (int32_t)floorf(pll_pos_)); + float delta_pos = (float)(shadow_count_ - (int32_t)floorf(pos_estimate_)); + float delta_pos_cpr = (float)(count_in_cpr_ - (int32_t)floorf(pos_cpr)); + delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * (float)(config_.cpr)); // pll feedback - pll_pos_ += current_meas_period * pll_kp_ * delta_pos; - pll_vel_ += current_meas_period * pll_ki_ * delta_pos; + pos_estimate_ += current_meas_period * pll_kp_ * delta_pos; + pos_cpr += current_meas_period * pll_kp_ * delta_pos_cpr; + pos_cpr = fmodf_pos(pos_cpr, (float)(config_.cpr)); + pll_vel_ += current_meas_period * pll_ki_ * delta_pos_cpr; // Assign output arguments - if (pos_estimate) *pos_estimate = pll_pos_; + if (pos_estimate) *pos_estimate = pos_estimate_; if (vel_estimate) *vel_estimate = pll_vel_; if (phase_output) *phase_output = phase_; return true; diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 57194e75..39139e1f 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -55,7 +55,7 @@ public: int32_t offset_ = 0; float phase_ = 0.0f; // [rad] float pos_estimate_ = 0.0f; // [rad] - float pll_pos_ = 0.0f; // [rad] + float pos_cpr = 0.0f; // [rad] float pll_vel_ = 0.0f; // [rad/s] float pll_kp_ = 0.0f; // [rad/s / rad] float pll_ki_ = 0.0f; // [(rad/s^2) / rad] @@ -71,7 +71,7 @@ public: make_protocol_property("offset", &offset_), make_protocol_property("phase", &phase_), make_protocol_property("pos_estimate", &pos_estimate_), - make_protocol_property("pll_pos", &pll_pos_), + make_protocol_property("pos_cpr", &pos_cpr), make_protocol_property("pll_vel", &pll_vel_), make_protocol_property("pll_kp", &pll_kp_), make_protocol_property("pll_ki", &pll_ki_), diff --git a/Firmware/MotorControl/utils.h b/Firmware/MotorControl/utils.h index 158065a0..52e7f98b 100644 --- a/Firmware/MotorControl/utils.h +++ b/Firmware/MotorControl/utils.h @@ -7,6 +7,7 @@ extern "C" { #endif #include +#include /** * @brief Unique ID register address location @@ -94,6 +95,14 @@ static inline float wrap_pm_pi(float theta) { return wrap_pm(theta, M_PI); } +// like fmodf, but always positive +static inline float fmodf_pos(float x, float y) { + float out = fmodf(x, y); + if (out < 0.0f) + out += y; + return out; +} + // Compute rising edge timings (0.0 - 1.0) as a function of alpha-beta // as per the magnitude invariant clarke transform // The magnitude of the alpha-beta vector may not be larger than sqrt(3)/2 diff --git a/tools/odrive/shell.py b/tools/odrive/shell.py index af2e9432..19fee9dd 100644 --- a/tools/odrive/shell.py +++ b/tools/odrive/shell.py @@ -22,7 +22,7 @@ def print_help(args): print('Type "odrv0." and press ') print('This will present you with all the properties that you can reference') print('') - print('For example: "odrv0.motor0.encoder.pll_pos"') + print('For example: "odrv0.motor0.encoder.pos_estimate"') print('will print the current encoder position on motor 0') print('and "odrv0.motor0.pos_setpoint = 10000"') print('will send motor0 to 10000') diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index eb052947..de4c6d8a 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -342,13 +342,13 @@ class TestClosedLoopControl(AxisTest): time.sleep(0.001) test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_CLOSED_LOOP_CONTROL) time.sleep(0.1) # give the PLL some time to settle - init_pos = axis_ctx.handle.encoder.pll_pos + init_pos = axis_ctx.handle.encoder.pos_estimate axis_ctx.handle.controller.set_pos_setpoint(init_pos+1000, 0, 0) time.sleep(0.5) - test_assert_eq(axis_ctx.handle.encoder.pll_pos, init_pos+1000, range=200) + test_assert_eq(axis_ctx.handle.encoder.pos_estimate, init_pos+1000, range=200) axis_ctx.handle.controller.set_pos_setpoint(init_pos-1000, 0, 0) time.sleep(0.5) - test_assert_eq(axis_ctx.handle.encoder.pll_pos, init_pos-1000, range=400) + test_assert_eq(axis_ctx.handle.encoder.pos_estimate, init_pos-1000, range=400) logger.debug("closed loop control: test vel_limit") axis_ctx.handle.controller.set_pos_setpoint(50000, 0, 0) @@ -551,7 +551,7 @@ class TestVelCtrlVsPosCtrl(DualAxisTest): # Turn to some position logger.debug("using {} as driver against load, vel=100000...".format(driver_ctx.name)) set_limits(driver_ctx, logger, vel_limit=100000, current_limit=50) - init_pos = driver_ctx.handle.encoder.pll_pos + init_pos = driver_ctx.handle.encoder.pos_estimate driver_ctx.handle.controller.set_pos_setpoint(init_pos + 100000, 0, 0) request_state(driver_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) for _ in range(int(4000/5)): @@ -563,7 +563,7 @@ class TestVelCtrlVsPosCtrl(DualAxisTest): logger.debug("using {} as driver against load, vel=20000...".format(driver_ctx.name)) set_limits(driver_ctx, logger, vel_limit=20000, current_limit=50) - init_pos = driver_ctx.handle.encoder.pll_pos + init_pos = driver_ctx.handle.encoder.pos_estimate driver_ctx.handle.controller.set_pos_setpoint(init_pos + 100000, 0, 0) request_state(driver_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) #for _ in range(int(5*4000/5)): @@ -580,6 +580,6 @@ class TestVelCtrlVsPosCtrl(DualAxisTest): ## Turn to another position #logger.debug("controlling against load, vel=40000...") #set_limits(axis1_ctx, logger, vel_limit=40000, current_limit=20) - #init_pos = axis1_ctx.handle.encoder.pll_pos + #init_pos = axis1_ctx.handle.encoder.pos_estimate #axis1_ctx.handle.controller.set_pos_setpoint(init_pos + 100000, 0, 0) #request_state(axis1_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) diff --git a/tools/odrivetool b/tools/odrivetool index 4afa2927..add4c0c6 100755 --- a/tools/odrivetool +++ b/tools/odrivetool @@ -100,8 +100,8 @@ try: # If you want to plot different values, change them here. # You can plot any number of values concurrently. - start_liveplotter(lambda: [my_odrive.motor0.encoder.pll_pos, - my_odrive.motor1.encoder.pll_pos]) + start_liveplotter(lambda: [my_odrive.motor0.encoder.pos_estimate, + my_odrive.motor1.encoder.pos_estimate]) elif args.command == 'drv-status': from odrive.utils import print_drv_regs From be50b6ee7409cc34fbb23688633de0f0d7a976f6 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sun, 15 Apr 2018 01:07:54 -0700 Subject: [PATCH 070/215] split set_count into linear and circular --- Firmware/MotorControl/encoder.cpp | 28 +++++++++++++++++++--------- Firmware/MotorControl/encoder.hpp | 3 ++- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 7f573752..24585d47 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -36,7 +36,7 @@ void Encoder::setup() { // TODO: disable interrupt once we found the index void Encoder::enc_index_cb() { if (config_.use_index && !index_found_) { - set_count(0); + set_circular_count(0); if (config_.pre_calibrated) { offset_ = config_.offset; is_ready_ = true; @@ -46,7 +46,7 @@ void Encoder::enc_index_cb() { } // Function that sets the current encoder count to a desired 32-bit value. -void Encoder::set_count(int32_t count) { +void Encoder::set_linear_count(int32_t count) { // Disable interrupts to make a critical section to avoid race condition uint32_t prim = __get_PRIMASK(); __disable_irq(); @@ -54,19 +54,29 @@ void Encoder::set_count(int32_t count) { // Update states shadow_count_ = count; pos_estimate_ = (float)count; - count_in_cpr_ = mod(count, config_.cpr); - pos_cpr = (float)count_in_cpr_; - - // Offset and state must be shifted by the same amount - offset_ += count - shadow_count_; - offset_ = mod(offset_, config_.cpr); - //Write hardware last hw_config_.timer->Instance->CNT = count; __set_PRIMASK(prim); } +// Function that sets the CPR circular tracking encoder count to a desired 32-bit value. +// Note that this will get mod'ed down to [0, cpr) +void Encoder::set_circular_count(int32_t count) { + // Disable interrupts to make a critical section to avoid race condition + uint32_t prim = __get_PRIMASK(); + __disable_irq(); + + // Offset and state must be shifted by the same amount + offset_ += count - count_in_cpr_; + offset_ = mod(offset_, config_.cpr); + // Update states + count_in_cpr_ = mod(count, config_.cpr); + pos_cpr = (float)count_in_cpr_; + + __set_PRIMASK(prim); +} + // @brief Slowly turns the motor in one direction until the // encoder index is found. diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 39139e1f..841127f4 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -35,7 +35,8 @@ public: void enc_index_cb(); - void set_count(int32_t count); + void set_linear_count(int32_t count); + void set_circular_count(int32_t count); bool calib_enc_offset(float voltage_magnitude); bool scan_for_enc_idx(float omega, float voltage_magnitude); From 57f6b71321180a739227f73ccff7cb8592f04d70 Mon Sep 17 00:00:00 2001 From: Matthew Moore Date: Sun, 15 Apr 2018 12:52:37 -0700 Subject: [PATCH 071/215] Change encoder.calibrated to encoder.manually_calibrated --- Firmware/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firmware/README.md b/Firmware/README.md index 214afe68..2acd6474 100644 --- a/Firmware/README.md +++ b/Firmware/README.md @@ -251,7 +251,7 @@ If you have an encoder with an index (Z) signal, you may avoid having to do the * Since you will only do this once, it is recommended that you mechanically disengage the motor from anything other than the encoder, so it can spin freely. * All the parameters we will be modifying are in the motor structs at the top of [MotorControl/low_level.c](MotorControl/low_level.c). -* Set `.encoder.use_index = true` and `.encoder.calibrated = false`. +* Set `.encoder.use_index = true` and `.encoder.manually_calibrated = false`. * Flash this configuration, and let the motor scan for the index pulse and then complete the encoder calibration. * Run `explore_odrive.py`, check [Communicating over USB or UART](#communicating-over-usb-or-uart) for instructions on how to do that. * Enter the following to print out the calibration parameters (substitute the motor number you are calibrating for ``): @@ -259,7 +259,7 @@ If you have an encoder with an index (Z) signal, you may avoid having to do the * `my_odrive.motor.encoder.motor_dir` - This should print 1 or -1. * Copy these numbers to the corresponding entries in low_level.c: `.encoder.encoder_offset` and `.encoder.motor_dir`. * _Warning_: Please be careful to enter the correct numbers, and not to confuse the motor channels. Incorrect values may cause the motor to spin out of control. -* Set `.encoder.calibrated = true`. +* Set `.encoder.manually_calibrated = true`. * Flash this configuration and check that the motor scans for the index pulse but skips the encoder calibration. * Congratulations, you are now done. You may now attach the motor to your mechanical load. * If you wish to scan for the index pulse in the other direction (if for example your axis usually starts close to a hard-stop), you can set a negative value in `.encoder.idx_search_speed`. From 7d99825988e3072b217e6284f0c5950b85528818 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sun, 15 Apr 2018 15:55:36 -0700 Subject: [PATCH 072/215] snap pll_vel to 0 to avoid jitter --- Firmware/MotorControl/encoder.cpp | 2 ++ tools/odrive/tests.py | 1 + tools/run_tests.py | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 24585d47..f2caa9b2 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -244,6 +244,8 @@ bool Encoder::update(float* pos_estimate, float* vel_estimate, float* phase_outp pos_cpr += current_meas_period * pll_kp_ * delta_pos_cpr; pos_cpr = fmodf_pos(pos_cpr, (float)(config_.cpr)); pll_vel_ += current_meas_period * pll_ki_ * delta_pos_cpr; + if (fabsf(pll_vel_) < 0.5f * current_meas_period * pll_ki_) + pll_vel_ = 0.0f; //align delta-sigma on zero to prevent jitter // Assign output arguments if (pos_estimate) *pos_estimate = pos_estimate_; diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index de4c6d8a..20ade97c 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -404,6 +404,7 @@ class TestHighVelocity(AxisTest): self._brake = brake def check_preconditions(self, axis_ctx: AxisTestContext, logger): + time.sleep(2.5) #delay in case load needs time to stop moving super(TestHighVelocity, self).check_preconditions(axis_ctx, logger) test_assert_eq(axis_ctx.handle.motor.is_calibrated, True) test_assert_eq(axis_ctx.handle.encoder.is_ready, True) diff --git a/tools/run_tests.py b/tools/run_tests.py index dc897fde..7e178c97 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -48,7 +48,7 @@ else: all_tests.append(TestEncoderOffsetCalibration(pass_if_ready=True)) #all_tests.append(TestHighVelocity()) -all_tests.append(TestHighVelocityInViscousFluid(load_current=60, driver_current=70)) +all_tests.append(TestHighVelocityInViscousFluid(load_current=20, driver_current=40)) # all_tests.append(TestVelCtrlVsPosCtrl()) # TODO: test step/dir # TODO: test sensorless From d558d554f5e8f01b9b4b4a38c96d1e1262366626 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sun, 15 Apr 2018 17:00:51 -0700 Subject: [PATCH 073/215] add speed ratings --- tools/odrive/tests.py | 18 ++++++++++++------ tools/run_tests.py | 2 +- tools/test-rig.yaml | 16 ++++++++++++---- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index 20ade97c..f192c301 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -404,18 +404,22 @@ class TestHighVelocity(AxisTest): self._brake = brake def check_preconditions(self, axis_ctx: AxisTestContext, logger): - time.sleep(2.5) #delay in case load needs time to stop moving + # time.sleep(2.5) #delay in case load needs time to stop moving super(TestHighVelocity, self).check_preconditions(axis_ctx, logger) test_assert_eq(axis_ctx.handle.motor.is_calibrated, True) test_assert_eq(axis_ctx.handle.encoder.is_ready, True) def run_test(self, axis_ctx: AxisTestContext, logger): - # Calculate theoretical max velocity in encoder counts per second based on the nominal - # V_bus and motor KV rating - max_rpm = axis_ctx.odrv_ctx.yaml['vbus-voltage'] * axis_ctx.yaml['motor-kv'] - rated_limit = max_rpm / 60 * axis_ctx.yaml['encoder-cpr'] - expected_limit = rated_limit + # Calculate theoretical max velocity in rpm based on the nominal + # V_bus and motor KV rating. If we are using a higher bus voltage than rated, use rated voltage + voltage_for_speed = min(axis_ctx.odrv_ctx.yaml['vbus-voltage'], axis_ctx.yaml['motor-max-voltage']) + base_speed_rpm = voltage_for_speed * axis_ctx.yaml['motor-kv'] + #but don't go over encoder max rpm + rated_rpm = min(base_speed_rpm, axis_ctx.yaml['encoder-max-rpm']) + #convert to count/s + rated_limit = rated_rpm / 60 * axis_ctx.yaml['encoder-cpr'] + expected_limit = rated_limit # The KV-rating assumes square-waves on the motor phases (hexagonal space vector trajectory) # whereas the ODrive modulates the space vector around a circular trajectory. # See Fig 4.28 here: http://krex.k-state.edu/dspace/bitstream/handle/2097/1507/JamesMevey2009.pdf @@ -441,6 +445,7 @@ class TestHighVelocity(AxisTest): else: axis_ctx.handle.motor.config.current_lim = self._override_current_limit axis_ctx.handle.controller.config.vel_limit = rated_limit + axis_ctx.handle.controller.set_vel_setpoint(0, 0) request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) logger.debug("Drive current {}A, Load current {}A".format(axis_ctx.handle.motor.config.current_lim, self._load_current)) @@ -518,6 +523,7 @@ class TestHighVelocityInViscousFluid(DualAxisTest): load_ctx.handle.controller.config.vel_limit = 20000 # this is not really relevant load_ctx.handle.motor.config.current_lim = self._load_current load_ctx.odrv_ctx.handle.config.brake_resistance = 0 # disable brake resistance, the power will go into the bus + load_ctx.handle.controller.set_vel_setpoint(0, 0) request_state(load_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) diff --git a/tools/run_tests.py b/tools/run_tests.py index 7e178c97..c6fe22b7 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -48,7 +48,7 @@ else: all_tests.append(TestEncoderOffsetCalibration(pass_if_ready=True)) #all_tests.append(TestHighVelocity()) -all_tests.append(TestHighVelocityInViscousFluid(load_current=20, driver_current=40)) +all_tests.append(TestHighVelocityInViscousFluid(load_current=35, driver_current=45)) # all_tests.append(TestVelCtrlVsPosCtrl()) # TODO: test step/dir # TODO: test sensorless diff --git a/tools/test-rig.yaml b/tools/test-rig.yaml index 7e84dec8..c1a16ebf 100644 --- a/tools/test-rig.yaml +++ b/tools/test-rig.yaml @@ -17,16 +17,20 @@ odrives: motor-pole-pairs: 7 motor-direction: 1 motor-kv: 190 - motor-max-current: 50 + motor-max-current: 70 + motor-max-voltage: 40 encoder-cpr: 8192 + encoder-max-rpm: 7000 - name: 'black' motor-phase-resistance: 0.028 motor-phase-inductance: 1.6e-05 motor-pole-pairs: 7 motor-direction: -1 motor-kv: 270 - motor-max-current: 50 + motor-max-current: 70 + motor-max-voltage: 32 encoder-cpr: 8192 + encoder-max-rpm: 7000 - name: bottom-odrive board-version: v3.5-24V serial-number: "3661335E3037" @@ -43,16 +47,20 @@ odrives: motor-pole-pairs: 7 motor-direction: 1 motor-kv: 270 - motor-max-current: 50 + motor-max-current: 70 + motor-max-voltage: 32 encoder-cpr: 8192 + encoder-max-rpm: 7000 - name: 'yellow' motor-phase-resistance: 0.0245 motor-phase-inductance: 2.03e-05 motor-pole-pairs: 7 motor-direction: -1 motor-kv: 190 - motor-max-current: 50 + motor-max-current: 70 + motor-max-voltage: 40 encoder-cpr: 8192 + encoder-max-rpm: 7000 # Mechanical couplings couplings: From fcb88e105e223ae499f29d6b0309f94760048317 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sun, 15 Apr 2018 18:17:17 -0700 Subject: [PATCH 074/215] rename test rig to parallel --- tools/run_tests.py | 2 +- tools/{test-rig.yaml => test-rig-parallel.yaml} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tools/{test-rig.yaml => test-rig-parallel.yaml} (100%) diff --git a/tools/run_tests.py b/tools/run_tests.py index c6fe22b7..62d51bf4 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -25,7 +25,7 @@ parser.add_argument("--ignore", metavar='DEVICE', action='store', nargs='+', help="Ignore one or more ODrives or axes") parser.add_argument("--test-rig-yaml", type=argparse.FileType('r'), help="test rig YAML file") -parser.set_defaults(test_rig_yaml=script_path + '/test-rig.yaml') +parser.set_defaults(test_rig_yaml=script_path + '/test-rig-parallel.yaml') parser.set_defaults(ignore=[]) args = parser.parse_args() diff --git a/tools/test-rig.yaml b/tools/test-rig-parallel.yaml similarity index 100% rename from tools/test-rig.yaml rename to tools/test-rig-parallel.yaml From 74cbaee70989608c413e14de667609fddd135bbe Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sun, 15 Apr 2018 21:46:33 -0700 Subject: [PATCH 075/215] add loopback test group --- tools/odrive/tests.py | 3 +++ tools/run_tests.py | 24 ++++++++++++++---------- tools/test-rig-loopback.yaml | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 tools/test-rig-loopback.yaml diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index f192c301..bc3a1339 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -538,6 +538,9 @@ class TestHighVelocityInViscousFluid(DualAxisTest): request_state(load_ctx, AXIS_STATE_IDLE) request_state(driver_ctx, AXIS_STATE_IDLE) +# class TestSelfLoadedPosVelDistribution(DualAxisTest): + + class TestVelCtrlVsPosCtrl(DualAxisTest): """ Uses one ODrive as a load operating in velocity control mode. diff --git a/tools/run_tests.py b/tools/run_tests.py index 62d51bf4..96773d86 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -25,12 +25,14 @@ parser.add_argument("--ignore", metavar='DEVICE', action='store', nargs='+', help="Ignore one or more ODrives or axes") parser.add_argument("--test-rig-yaml", type=argparse.FileType('r'), help="test rig YAML file") -parser.set_defaults(test_rig_yaml=script_path + '/test-rig-parallel.yaml') +# parser.set_defaults(test_rig_yaml=script_path + '/test-rig-parallel.yaml') parser.set_defaults(ignore=[]) args = parser.parse_args() +test_rig_yaml = yaml.load(args.test_rig_yaml) # TODO: add --only option + all_tests = [] if not args.skip_boring_tests: all_tests.append(TestFlashAndErase()) @@ -47,19 +49,21 @@ else: all_tests.append(TestDiscoverAndGotoIdle()) all_tests.append(TestEncoderOffsetCalibration(pass_if_ready=True)) -#all_tests.append(TestHighVelocity()) -all_tests.append(TestHighVelocityInViscousFluid(load_current=35, driver_current=45)) -# all_tests.append(TestVelCtrlVsPosCtrl()) -# TODO: test step/dir -# TODO: test sensorless -# TODO: test ASCII protocol -# TODO: test protocol over UART +if 'test-rig-parallel.yaml' in test_rig_yaml: + #all_tests.append(TestHighVelocity()) + all_tests.append(TestHighVelocityInViscousFluid(load_current=35, driver_current=45)) + # all_tests.append(TestVelCtrlVsPosCtrl()) + # TODO: test step/dir + # TODO: test sensorless + # TODO: test ASCII protocol + # TODO: test protocol over UART +elif 'test-rig-loopback.yaml' in test_rig_yaml: + pass + print(str(args.ignore)) logger = Logger() - -test_rig_yaml = yaml.load(args.test_rig_yaml) os.chdir(script_path + '/../Firmware') # Build a dictionary of odrive test contexts by name diff --git a/tools/test-rig-loopback.yaml b/tools/test-rig-loopback.yaml new file mode 100644 index 00000000..6cb15c75 --- /dev/null +++ b/tools/test-rig-loopback.yaml @@ -0,0 +1,36 @@ + +odrives: + - name: odrive-48V + board-version: v3.5-48V + serial-number: "3660335E3037" + brake-resistance: 0.47 + uart: /dev/serial/by-id/[not-yet-used] + usb: auto + programmer: '533f7506493f49514454193f' + vbus-voltage: 24 # [V] + max-brake-power: 150 # [W] + axes: + - name: 'M0' + motor-phase-resistance: 0.0245 + motor-phase-inductance: 2.03e-05 + motor-pole-pairs: 7 + motor-direction: 1 + motor-kv: 190 + motor-max-current: 70 + motor-max-voltage: 40 + encoder-cpr: 8192 + encoder-max-rpm: 7000 + - name: 'M1' + motor-phase-resistance: 0.0245 + motor-phase-inductance: 2.03e-05 + motor-pole-pairs: 7 + motor-direction: -1 + motor-kv: 190 + motor-max-current: 70 + motor-max-voltage: 40 + encoder-cpr: 8192 + encoder-max-rpm: 7000 + +# Mechanical couplings +couplings: + - [ odrive-48V.M0, odrive-48V.M1 ] \ No newline at end of file From 6e066b45f3a6a046b913ac7d5e1b210954807880 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sun, 15 Apr 2018 23:18:58 -0700 Subject: [PATCH 076/215] finish spiral test --- tools/odrive/tests.py | 118 ++++++++++++++++++++++++++++------- tools/run_tests.py | 7 ++- tools/test-rig-loopback.yaml | 2 + tools/test-rig-parallel.yaml | 2 + 4 files changed, 105 insertions(+), 24 deletions(-) diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index bc3a1339..9e3d47f1 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -8,6 +8,7 @@ import threading import odrive.discovery from odrive.enums import * import odrive.utils +import numpy as np import functools print = functools.partial(print, flush=True) @@ -131,6 +132,27 @@ def set_limits(axis_ctx: AxisTestContext, logger, vel_limit=20000, current_limit axis_ctx.handle.motor.config.current_lim = current_limit axis_ctx.handle.controller.config.vel_limit = vel_limit +def get_max_rpm(axis_ctx: AxisTestContext): + + # Calculate theoretical max velocity in rpm based on the nominal + # V_bus and motor KV rating. + # The KV-rating assumes square-waves on the motor phases (hexagonal space vector trajectory) + # whereas the ODrive modulates the space vector around a circular trajectory. + # See Fig 4.28 here: http://krex.k-state.edu/dspace/bitstream/handle/2097/1507/JamesMevey2009.pdf + effective_bus_voltage = axis_ctx.odrv_ctx.yaml['vbus-voltage'] + effective_bus_voltage *= (2/math.sqrt(3)) / (4/math.pi) # roughtly 90% + # The ODrive only goes to 80% modulation depth in order to save some time for the ADC measurements. + # See FOC_current in motor.cpp. + effective_bus_voltage *= 0.8 + + # If we are using a higher bus voltage than rated: use rated voltage, + # since that is an effective speed rating of the motor + voltage_for_speed = min(effective_bus_voltage, axis_ctx.yaml['motor-max-voltage']) + base_speed_rpm = voltage_for_speed * axis_ctx.yaml['motor-kv'] + + #but don't go over encoder max rpm + rated_rpm = min(base_speed_rpm, axis_ctx.yaml['encoder-max-rpm']) + return rated_rpm class ODriveTest(ABC): """ @@ -410,24 +432,8 @@ class TestHighVelocity(AxisTest): test_assert_eq(axis_ctx.handle.encoder.is_ready, True) def run_test(self, axis_ctx: AxisTestContext, logger): - # Calculate theoretical max velocity in rpm based on the nominal - # V_bus and motor KV rating. If we are using a higher bus voltage than rated, use rated voltage - voltage_for_speed = min(axis_ctx.odrv_ctx.yaml['vbus-voltage'], axis_ctx.yaml['motor-max-voltage']) - base_speed_rpm = voltage_for_speed * axis_ctx.yaml['motor-kv'] - #but don't go over encoder max rpm - rated_rpm = min(base_speed_rpm, axis_ctx.yaml['encoder-max-rpm']) - #convert to count/s - rated_limit = rated_rpm / 60 * axis_ctx.yaml['encoder-cpr'] - + rated_limit = get_max_rpm(axis_ctx) / 60 * axis_ctx.yaml['encoder-cpr'] expected_limit = rated_limit - # The KV-rating assumes square-waves on the motor phases (hexagonal space vector trajectory) - # whereas the ODrive modulates the space vector around a circular trajectory. - # See Fig 4.28 here: http://krex.k-state.edu/dspace/bitstream/handle/2097/1507/JamesMevey2009.pdf - expected_limit *= (2/math.sqrt(3)) / (4/math.pi) # roughtly 90% - - # The ODrive only goes to 80% modulation depth in order to save some time for the ADC measurements. - # See FOC_current in motor.cpp. - expected_limit *= 0.8 # TODO: remove the following two lines, but for now we want to stay away from the modulation depth limit expected_limit *= 0.6 @@ -451,10 +457,10 @@ class TestHighVelocity(AxisTest): logger.debug("Drive current {}A, Load current {}A".format(axis_ctx.handle.motor.config.current_lim, self._load_current)) ramp_up_time = 15.0 - t_0 = time.monotonic() - last_print = t_0 max_measured_vel = 0.0 logger.debug("ramping to {} over {} s".format(rated_limit, ramp_up_time)) + t_0 = time.monotonic() + last_print = t_0 while True: ratio = (time.monotonic() - t_0) / ramp_up_time if ratio >= 1: @@ -520,7 +526,6 @@ class TestHighVelocityInViscousFluid(DualAxisTest): # Set up viscous fluid load logger.debug("activating load on {}...".format(load_ctx.name)) load_ctx.handle.controller.config.vel_integrator_gain = 0 - load_ctx.handle.controller.config.vel_limit = 20000 # this is not really relevant load_ctx.handle.motor.config.current_lim = self._load_current load_ctx.odrv_ctx.handle.config.brake_resistance = 0 # disable brake resistance, the power will go into the bus load_ctx.handle.controller.set_vel_setpoint(0, 0) @@ -538,8 +543,79 @@ class TestHighVelocityInViscousFluid(DualAxisTest): request_state(load_ctx, AXIS_STATE_IDLE) request_state(driver_ctx, AXIS_STATE_IDLE) -# class TestSelfLoadedPosVelDistribution(DualAxisTest): +class TestSelfLoadedPosVelDistribution(DualAxisTest): + """ + Uses an ODrive mechanically connected to itself to test a distribution of + speeds and currents. Since it's connected to itself, we can be a lot less + strict about the brake resistor power use. + """ + def __init__(self, rpm_range=1000, load_current_range=10, driver_current_lim=20): + self._rpm_range = rpm_range + self._load_current_range = load_current_range + self._driver_current_lim = driver_current_lim + def run_test(self, axis0_ctx: AxisTestContext, axis1_ctx: AxisTestContext, logger): + load_ctx = axis0_ctx + driver_ctx = axis1_ctx + + logger.debug("Iload range: {} A, Idriver: {} A".format(self._load_current_range, self._driver_current_lim)) + + # max speed for rig in counts/s for each encoder (may be different CPR) + max_rpm = min(self._rpm_range, get_max_rpm(driver_ctx), get_max_rpm(load_ctx)) + driver_max_speed = max_rpm / 60 * driver_ctx.yaml['encoder-cpr'] + load_max_speed = max_rpm / 60 * load_ctx.yaml['encoder-cpr'] + logger.debug("RPM range: {} = driver {} = load {}".format(max_rpm, driver_max_speed, load_max_speed)) + + # Set up velocity controlled load + logger.debug("activating load on {}".format(load_ctx.name)) + load_ctx.handle.controller.config.vel_integrator_gain = 0 + load_ctx.handle.controller.config.vel_limit = load_max_speed + load_ctx.handle.motor.config.current_lim = 0 #load current to be set during runtime + load_ctx.handle.controller.set_vel_setpoint(0, 0) # vel sign also set during runtime + request_state(load_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) + + # Set up velocity controlled driver + logger.debug("activating driver on {}".format(driver_ctx.name)) + driver_ctx.handle.motor.config.current_lim = self._driver_current_lim + driver_ctx.handle.controller.config.vel_limit = driver_max_speed + driver_ctx.handle.controller.set_vel_setpoint(0, 0) + request_state(driver_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) + + # Spiral parameters + command_rate = 500.0 #Hz (nominal, achived rate is less due to time.sleep approx) + test_duration = 15.0 #s + num_cycles = 3.0 # number of spiral "rotations" + + t_0 = time.monotonic() + t_ratio = 0 + last_print = t_0 + while t_ratio < 1: + t_ratio = (time.monotonic() - t_0) / test_duration + phase = 2 * math.pi * num_cycles * t_ratio + driver_speed = t_ratio * driver_max_speed * math.sin(phase) + # print(driver_speed) + driver_ctx.handle.controller.set_vel_setpoint(driver_speed, 0) + load_current = t_ratio * self._load_current_range * math.cos(phase) + Iload_mag = abs(load_current) + Iload_sign = np.sign(load_current) + # print("I: {}, vel {}".format(Iload_mag, Iload_sign * load_max_speed)) + load_ctx.handle.motor.config.current_lim = Iload_mag + load_ctx.handle.controller.set_vel_setpoint(Iload_sign * load_max_speed, 0) + + test_assert_no_error(driver_ctx) + test_assert_no_error(load_ctx) + + # log progress + if time.monotonic() - last_print > 1: + last_print = time.monotonic() + logger.debug("Envelope -- vel: {:.2f}, I: {:.2f}".format(t_ratio * driver_max_speed, t_ratio * self._load_current_range)) + + time.sleep(1/command_rate) + + request_state(load_ctx, AXIS_STATE_IDLE) + request_state(driver_ctx, AXIS_STATE_IDLE) + test_assert_no_error(driver_ctx) + test_assert_no_error(load_ctx) class TestVelCtrlVsPosCtrl(DualAxisTest): """ diff --git a/tools/run_tests.py b/tools/run_tests.py index 96773d86..c6bc9999 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -49,7 +49,7 @@ else: all_tests.append(TestDiscoverAndGotoIdle()) all_tests.append(TestEncoderOffsetCalibration(pass_if_ready=True)) -if 'test-rig-parallel.yaml' in test_rig_yaml: +if test_rig_yaml['type'] == 'parallel': #all_tests.append(TestHighVelocity()) all_tests.append(TestHighVelocityInViscousFluid(load_current=35, driver_current=45)) # all_tests.append(TestVelCtrlVsPosCtrl()) @@ -57,8 +57,9 @@ if 'test-rig-parallel.yaml' in test_rig_yaml: # TODO: test sensorless # TODO: test ASCII protocol # TODO: test protocol over UART -elif 'test-rig-loopback.yaml' in test_rig_yaml: - pass +elif test_rig_yaml['type'] == 'loopback': + all_tests.append(TestSelfLoadedPosVelDistribution( + rpm_range=2500, load_current_range=50, driver_current_lim=60)) print(str(args.ignore)) diff --git a/tools/test-rig-loopback.yaml b/tools/test-rig-loopback.yaml index 6cb15c75..a55f6e72 100644 --- a/tools/test-rig-loopback.yaml +++ b/tools/test-rig-loopback.yaml @@ -1,4 +1,6 @@ +type: loopback + odrives: - name: odrive-48V board-version: v3.5-48V diff --git a/tools/test-rig-parallel.yaml b/tools/test-rig-parallel.yaml index c1a16ebf..e7559105 100644 --- a/tools/test-rig-parallel.yaml +++ b/tools/test-rig-parallel.yaml @@ -1,4 +1,6 @@ +type: parallel + # ODrives odrives: - name: top-odrive From 9593ee4f4aa35b45aba5a62a76e7b138aa76825e Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Tue, 17 Apr 2018 00:41:31 -0700 Subject: [PATCH 077/215] change to 48V for loopback --- tools/odrive/tests.py | 2 +- tools/run_tests.py | 2 +- tools/test-rig-loopback.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index 9e3d47f1..182f70aa 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -583,7 +583,7 @@ class TestSelfLoadedPosVelDistribution(DualAxisTest): # Spiral parameters command_rate = 500.0 #Hz (nominal, achived rate is less due to time.sleep approx) - test_duration = 15.0 #s + test_duration = 20.0 #s num_cycles = 3.0 # number of spiral "rotations" t_0 = time.monotonic() diff --git a/tools/run_tests.py b/tools/run_tests.py index c6bc9999..f2f67a19 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -59,7 +59,7 @@ if test_rig_yaml['type'] == 'parallel': # TODO: test protocol over UART elif test_rig_yaml['type'] == 'loopback': all_tests.append(TestSelfLoadedPosVelDistribution( - rpm_range=2500, load_current_range=50, driver_current_lim=60)) + rpm_range=3000, load_current_range=60, driver_current_lim=70)) print(str(args.ignore)) diff --git a/tools/test-rig-loopback.yaml b/tools/test-rig-loopback.yaml index a55f6e72..34989214 100644 --- a/tools/test-rig-loopback.yaml +++ b/tools/test-rig-loopback.yaml @@ -9,7 +9,7 @@ odrives: uart: /dev/serial/by-id/[not-yet-used] usb: auto programmer: '533f7506493f49514454193f' - vbus-voltage: 24 # [V] + vbus-voltage: 48 # [V] max-brake-power: 150 # [W] axes: - name: 'M0' From 8407721744acc642ff19b8625a85b30c2da2afa4 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Tue, 17 Apr 2018 03:21:03 -0700 Subject: [PATCH 078/215] fix external interrupts, step dir now working --- Firmware/.vscode/c_cpp_properties.json | 4 ++-- Firmware/Board/v3/Src/gpio.c | 6 ++++-- Firmware/Board/v3/Src/stm32f4xx_it.c | 8 ++++++++ Firmware/MotorControl/axis.cpp | 8 ++++---- Firmware/MotorControl/main.cpp | 2 ++ Firmware/tup.config.default | 1 - 6 files changed, 20 insertions(+), 9 deletions(-) diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index 5042079e..eab99cd5 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -27,8 +27,8 @@ "STM32F405xx", "USE_HAL_DRIVER", "HW_VERSION_MAJOR=3", - "HW_VERSION_MINOR=4", - "HW_VERSION_VOLTAGE=24", + "HW_VERSION_MINOR=5", + "HW_VERSION_VOLTAGE=48", "__weak=\"__attribute__((weak))\"", "__packed=\"__attribute__((__packed__))\"", "__GNUC__" diff --git a/Firmware/Board/v3/Src/gpio.c b/Firmware/Board/v3/Src/gpio.c index 91b3f3cd..fb163f71 100644 --- a/Firmware/Board/v3/Src/gpio.c +++ b/Firmware/Board/v3/Src/gpio.c @@ -138,8 +138,9 @@ void MX_GPIO_Init(void) HAL_GPIO_Init(nFAULT_GPIO_Port, &GPIO_InitStruct); /* EXTI interrupt init*/ - HAL_NVIC_SetPriority(EXTI2_IRQn, 0, 0); - HAL_NVIC_EnableIRQ(EXTI2_IRQn); + // TODO get Cube to not emit this + // HAL_NVIC_SetPriority(EXTI2_IRQn, 0, 0); + // HAL_NVIC_EnableIRQ(EXTI2_IRQn); } @@ -151,6 +152,7 @@ void MX_GPIO_Init(void) // no matter which port they belong to. IRQn_Type get_irq_number(uint16_t pin) { uint16_t pin_number = 0; + pin >>= 1; while (pin) { pin >>= 1; pin_number++; diff --git a/Firmware/Board/v3/Src/stm32f4xx_it.c b/Firmware/Board/v3/Src/stm32f4xx_it.c index cd96644a..a7fe3a12 100644 --- a/Firmware/Board/v3/Src/stm32f4xx_it.c +++ b/Firmware/Board/v3/Src/stm32f4xx_it.c @@ -341,6 +341,14 @@ void EXTI4_IRQHandler(void) HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4); } +/** +* @brief This function handles EXTI lines 5-9 interrupt. +*/ +void EXTI9_5_IRQHandler(void) +{ + HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_9); +} + /** * @brief This function handles EXTI lines 10-15 interrupt. */ diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index bde389d4..4486199a 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -25,6 +25,10 @@ Axis::Axis(const AxisHardwareConfig_t& hw_config, motor_.axis_ = this; } +static void step_cb_wrapper(void* ctx) { + reinterpret_cast(ctx)->step_cb(); +} + // @brief Sets up all components of the axis, // such as gate driver and encoder hardware. void Axis::setup() { @@ -56,10 +60,6 @@ bool Axis::wait_for_current_meas() { return osSignalWait(M_SIGNAL_PH_CURRENT_MEAS, PH_CURRENT_MEAS_TIMEOUT).status == osEventSignal; } -static void step_cb_wrapper(void* ctx) { - reinterpret_cast(ctx)->step_cb(); -} - // step/direction interface void Axis::step_cb() { if (enable_step_dir_) { diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 39a60356..75cd43d2 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -31,6 +31,7 @@ void save_configuration(void) { } void load_configuration(void) { + // Try to load configs if (NVM_init() || ConfigFormat::safe_load_config( &board_config, @@ -38,6 +39,7 @@ void load_configuration(void) { &controller_configs, &motor_configs, &axis_configs)) { + //If loading failed, restore defaults board_config = BoardConfig_t(); for (size_t i = 0; i < AXIS_COUNT; ++i) { encoder_configs[i] = EncoderConfig_t(); diff --git a/Firmware/tup.config.default b/Firmware/tup.config.default index 5cd89434..8ebb4402 100644 --- a/Firmware/tup.config.default +++ b/Firmware/tup.config.default @@ -3,4 +3,3 @@ #CONFIG_BOARD_VERSION=v3.5-24V CONFIG_USB_PROTOCOL=native CONFIG_UART_PROTOCOL=ascii -CONFIG_STEP_DIR=n From 87a04bd9f62850f69c76afef31383dde29e6179b Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Tue, 17 Apr 2018 18:01:41 -0700 Subject: [PATCH 079/215] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 16613f9a..32f3a08c 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ ODrive v3.3 and onward have 5V tolerant GPIO pins. To enable step/dir mode for the GPIO, please see [Setting the GPIO mode](Firmware/README.md#communication-configuration). There is also a new config variable called `counts_per_step`, which specifies how many encoder counts a "step" corresponds to. It can be any floating point value. -The maximum step rate is pending tests, but it should handle at least 16kHz. If you want's to test it, please be aware that the failure mode on too high step rates is expected to be that the motors shuts down and coasts. +The maximum step rate is pending tests, but it should handle at least 32kHz. If you want's to test it, please be aware that the failure mode on too high step rates is expected to be that the motors shuts down and coasts. Please be aware that there is no enable line right now, and the step/direction interface is enabled by default, and remains active as long as the ODrive is in position control mode. By default the ODrive starts in position control mode, so you don't need to send any commands over USB to get going. You can still send USB commands if you want to. From cd5b9014c1d90ca03cab563c629ad32b420a0209 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 17 Apr 2018 13:29:17 -0700 Subject: [PATCH 080/215] include PyWin32 as dependency for windows, add "Developer Preview" note --- tools/odrivetool | 8 +++++++- tools/setup.py | 3 ++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/tools/odrivetool b/tools/odrivetool index 0bcc39b7..b66db5c7 100755 --- a/tools/odrivetool +++ b/tools/odrivetool @@ -79,9 +79,15 @@ else: printer = lambda x: None logger = Logger(verbose=args.verbose) -logger.debug(str(args)) print("ODrive control utility v" + odrive.__version__) +if ".dev" in odrive.__version__: + print("") + logger.warn("Developer Preview") + print(" If you find issues, please report them") + print(" on https://github.com/madcowswe/ODrive/issues") + print(" or better yet, submit a pull request to fix it.") + print("") app_shutdown_token = Event() diff --git a/tools/setup.py b/tools/setup.py index 1c43b0a1..7fde17bd 100644 --- a/tools/setup.py +++ b/tools/setup.py @@ -74,7 +74,8 @@ setup( 'PyUSB', # Required to access USB devices from Python through libusb 'PySerial', # Required to access serial devices from Python 'IntelHex', # Used to by DFU to load firmware files - 'matplotlib' # Required to run the liveplotter + 'matplotlib', # Required to run the liveplotter + 'pywin32 >= 1.0;platform_system=="Windows"' # Required for fancy terminal features on Windows ], package_data={'': ['version.txt']}, include_package_data=True, From 8c6110e4992dc50e47c67e48cdc4a22b9c11c10f Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 17 Apr 2018 13:29:38 -0700 Subject: [PATCH 081/215] improve tostring dump of remote objects --- tools/odrive/remote_object.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/tools/odrive/remote_object.py b/tools/odrive/remote_object.py index 58da44ab..81182c77 100644 --- a/tools/odrive/remote_object.py +++ b/tools/odrive/remote_object.py @@ -85,6 +85,17 @@ class RemoteProperty(): # TODO: Currenly we wait for an ack here. Settle on the default guarantee. self._parent.__channel__.remote_endpoint_operation(self._id, buffer, True, 0) + def dump(self): + if self._name == "serial_number": + # special case: serial number should be displayed in hex (TODO: generalize) + val_str = "{:012X}".format(self.get_value()) + elif self._name == "error": + # special case: errors should be displayed in hex (TODO: generalize) + val_str = "0x{:04X}".format(self.get_value()) + else: + val_str = str(self.get_value()) + return "{} = {} ({})".format(self._name, val_str, self._property_type.__name__) + class RemoteFunction(object): """ Represents a callable function that maps to a function call on a remote object @@ -96,6 +107,10 @@ class RemoteFunction(object): raise ObjectDefinitionError("unspecified endpoint ID") self._trigger_id = int(id_str) + self._name = json_data.get("name", None) + if self._name is None: + self._name = "[anonymous]" + self._inputs = [] for param_json in json_data.get("arguments", []) + json_data.get("inputs", []): # TODO: deprecate "arguments" keyword param_json["mode"] = "r" @@ -108,6 +123,9 @@ class RemoteFunction(object): self._inputs[i].set_value(args[i]) self._parent.__channel__.remote_endpoint_operation(self._trigger_id, None, True, 0) + def dump(self): + return "{}({})".format(self._name, ", ".join("{}: {}".format(x._name, x._property_type.__name__) for x in self._inputs)) + class RemoteObject(object): """ Object with functions and properties that map to remote endpoints @@ -156,8 +174,21 @@ class RemoteObject(object): self.__sealed__ = True channel._channel_broken.subscribe(self._tear_down) + def dump(self, indent, depth): + if depth <= 0: + return "..." + lines = [] + for key, val in self._remote_attributes.items(): + if isinstance(val, RemoteObject): + val_str = indent + key + (": " if depth == 1 else ":\n") + val.dump(indent + " ", depth - 1) + else: + val_str = indent + val.dump() + lines.append(val_str) + return "\n".join(lines) + def __str__(self): - return str(dir(self)) # TODO: improve print output + return self.dump("", depth=2) + def __repr__(self): return self.__str__() From 0eddbfc11c44e0d21e0009f0b8c644df9538301b Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 17 Apr 2018 19:59:44 -0700 Subject: [PATCH 082/215] Add udev-setup command to odrivetool --- tools/odrive/utils.py | 13 +++++++++++++ tools/odrivetool | 5 +++++ tools/setup.py | 6 ++++++ 3 files changed, 24 insertions(+) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 810780d0..5834ff85 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -7,6 +7,8 @@ import sys import time import threading import platform +import subprocess +import os try: if platform.system() == 'Windows': @@ -109,6 +111,17 @@ def rate_test(device): FramePerSec = loopsPerSec/loopsPerFrame print("Frames per second: " + str(FramePerSec)) +def setup_udev_rules(logger): + if platform.system() != 'Linux': + logger.error("This command only makes sense on Linux") + if os.getuid() != 0: + logger.warn("you should run this as root, otherwise it will probably not work") + with open('/etc/udev/rules.d/50-odrive.rules', 'w') as file: + file.write('SUBSYSTEM=="usb", ATTR{idVendor}=="1209", ATTR{idProduct}=="0d3[0-9]", MODE="0666"\n') + subprocess.run(["udevadm", "control", "--reload-rules"], check=True) + subprocess.run(["udevadm", "trigger"], check=True) + logger.info('udev rules configured successfully') + ## Exceptions ## diff --git a/tools/odrivetool b/tools/odrivetool index b66db5c7..2feb9b2c 100755 --- a/tools/odrivetool +++ b/tools/odrivetool @@ -31,6 +31,7 @@ dfu_parser.add_argument('file', metavar='HEX', help='The .hex file to be flashed subparsers.add_parser('liveplotter', help="Upgrade the ODrive's Firmware") subparsers.add_parser('drv-status', help="Show status of the on-board DRV8301 chips (for debugging only)") subparsers.add_parser('rate-test', help="Estimate the average transmission bandwidth over USB") +subparsers.add_parser('udev-setup', help="Linux only: Gives users on your system permission to access the ODrive by installing udev rules") # General arguments parser.add_argument("-p", "--path", metavar="PATH", action="store", @@ -126,6 +127,10 @@ try: my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number) rate_test(my_odrive) + elif args.command == 'udev-setup': + from odrive.utils import setup_udev_rules + setup_udev_rules(logger) + else: raise Exception("unknown command: " + args.command) diff --git a/tools/setup.py b/tools/setup.py index 7fde17bd..a7ac5980 100644 --- a/tools/setup.py +++ b/tools/setup.py @@ -58,6 +58,12 @@ if creating_package: with open(version_file_path, mode='w') as version_file: version_file.write(version) +# TODO: find a better place for this +if not creating_package: + import platform + if platform.system() == 'Linux': + import odrive.utils + odrive.utils.setup_udev_rules(odrive.utils.Logger()) setup( name = 'odrive', From 7b9b0f884b73e2a0e1ed5886dcfca1def2f67805 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 17 Apr 2018 20:03:19 -0700 Subject: [PATCH 083/215] Add batch file for windows as a trampoline for the actual script The installation does not work unless pywin32 is already installed. See: https://github.com/mhammond/pywin32/issues/1197 --- tools/odrivetool.bat | 2 ++ tools/setup.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 tools/odrivetool.bat diff --git a/tools/odrivetool.bat b/tools/odrivetool.bat new file mode 100644 index 00000000..765e31cb --- /dev/null +++ b/tools/odrivetool.bat @@ -0,0 +1,2 @@ +@echo off +python %~dp0\odrivetool \ No newline at end of file diff --git a/tools/setup.py b/tools/setup.py index a7ac5980..85ddd5cd 100644 --- a/tools/setup.py +++ b/tools/setup.py @@ -68,7 +68,7 @@ if not creating_package: setup( name = 'odrive', packages = ['odrive', 'odrive.dfuse'], # this must be the same as the name above - scripts = ['odrivetool', 'odrive_demo.py'], + scripts = ['odrivetool', 'odrivetool.bat', 'odrive_demo.py'], version = version, description = 'Control utilities for the ODrive high performance motor controller', author = 'Oskar Weigl', @@ -81,7 +81,7 @@ setup( 'PySerial', # Required to access serial devices from Python 'IntelHex', # Used to by DFU to load firmware files 'matplotlib', # Required to run the liveplotter - 'pywin32 >= 1.0;platform_system=="Windows"' # Required for fancy terminal features on Windows + 'pywin32==222;platform_system=="Windows"' # Required for fancy terminal features on Windows ], package_data={'': ['version.txt']}, include_package_data=True, From 3eeffd6f7ecfc7d399865c8e25c6e70e275f49ae Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 17 Apr 2018 22:33:29 -0700 Subject: [PATCH 084/215] add checksum output --- tools/odrive/discovery.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/odrive/discovery.py b/tools/odrive/discovery.py index 18b5107a..fe4a37f0 100644 --- a/tools/odrive/discovery.py +++ b/tools/odrive/discovery.py @@ -53,6 +53,7 @@ def find_all(path, serial_number, printer("device responded on endpoint 0 with something that is not ASCII") return printer("JSON: " + json_string) + printer("JSON checksum: 0x{:02X} 0x{:02X}".format(json_crc16 & 0xff, (json_crc16 >> 8) & 0xff)) try: json_data = json.loads(json_string) except json.decoder.JSONDecodeError as error: From 9d2b56998ff59f616ca64d3b8466f1e8632976f6 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 21 Apr 2018 00:13:28 -0700 Subject: [PATCH 085/215] Update protocol.py --- tools/odrive/protocol.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/odrive/protocol.py b/tools/odrive/protocol.py index c7305b19..aced6cc9 100644 --- a/tools/odrive/protocol.py +++ b/tools/odrive/protocol.py @@ -6,12 +6,12 @@ import sys import abc -if (sys.version_info[0], sys.version_info[1]) >= (3, 4): +if sys.version_info >= (3, 4): ABC = abc.ABC else: ABC = abc.ABCMeta('ABC', (), {}) -if (sys.version_info[0], sys.version_info[1]) <= (3, 3): +if sys.version_info < (3, 3): from monotonic import monotonic time.monotonic = monotonic From 0ac2871deccbd9bbc25ccf114cbd648008483bb2 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 21 Apr 2018 14:27:37 -0700 Subject: [PATCH 086/215] drv fault only reads fault regs, add blackside loopback tests --- Firmware/MotorControl/motor.cpp | 6 +++--- Firmware/MotorControl/motor.hpp | 10 +++++----- tools/odrive/tests.py | 8 ++++---- tools/test-rig-loopback.yaml | 35 +++++++++++++++++++++++++++++++-- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 6f12474d..9558dc35 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -117,9 +117,9 @@ bool Motor::check_DRV_fault() { // Update DRV Fault Code drv_fault_ = DRV8301_getFaultType(&gate_driver_); // Update/Cache all SPI device registers - DRV_SPI_8301_Vars_t* local_regs = &gate_driver_regs_; - local_regs->RcvCmd = true; - DRV8301_readData(&gate_driver_, local_regs); + // DRV_SPI_8301_Vars_t* local_regs = &gate_driver_regs_; + // local_regs->RcvCmd = true; + // DRV8301_readData(&gate_driver_, local_regs); return false; }; return true; diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index ebc47b9c..7f9e9b08 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -180,11 +180,11 @@ public: make_protocol_property("max_allowed_current", ¤t_control_.max_allowed_current) ), make_protocol_object("gate_driver", - make_protocol_ro_property("drv_fault", &drv_fault_), - make_protocol_ro_property("status_reg_1", &gate_driver_regs_.Stat_Reg_1_Value), - make_protocol_ro_property("status_reg_2", &gate_driver_regs_.Stat_Reg_2_Value), - make_protocol_ro_property("ctrl_reg_1", &gate_driver_regs_.Ctrl_Reg_1_Value), - make_protocol_ro_property("ctrl_reg_2", &gate_driver_regs_.Ctrl_Reg_2_Value) + make_protocol_ro_property("drv_fault", &drv_fault_) + // make_protocol_ro_property("status_reg_1", &gate_driver_regs_.Stat_Reg_1_Value), + // make_protocol_ro_property("status_reg_2", &gate_driver_regs_.Stat_Reg_2_Value), + // make_protocol_ro_property("ctrl_reg_1", &gate_driver_regs_.Ctrl_Reg_1_Value), + // make_protocol_ro_property("ctrl_reg_2", &gate_driver_regs_.Ctrl_Reg_2_Value) ), make_protocol_object("timing_log", make_protocol_ro_property("TIMING_LOG_GENERAL", &timing_log_[TIMING_LOG_GENERAL]), diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index 182f70aa..43ef182b 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -61,13 +61,13 @@ def test_assert_eq(observed, expected, range=None, accuracy=None): def get_errors(axis_ctx: AxisTestContext): errors = [] if axis_ctx.handle.motor.error != 0: - errors.append("motor failed with error {:04X}".format(axis_ctx.handle.motor.error)) + errors.append("motor failed with error 0x{:04X}".format(axis_ctx.handle.motor.error)) if axis_ctx.handle.encoder.error != 0: - errors.append("encoder failed with error {:04X}".format(axis_ctx.handle.encoder.error)) + errors.append("encoder failed with error 0x{:04X}".format(axis_ctx.handle.encoder.error)) if axis_ctx.handle.sensorless_estimator.error != 0: - errors.append("sensorless_estimator failed with error {:04X}".format(axis_ctx.handle.sensorless_estimator.error)) + errors.append("sensorless_estimator failed with error 0x{:04X}".format(axis_ctx.handle.sensorless_estimator.error)) if axis_ctx.handle.error != 0: - errors.append("axis failed with error {:04X}".format(axis_ctx.handle.error)) + errors.append("axis failed with error 0x{:04X}".format(axis_ctx.handle.error)) elif len(errors) > 0: errors.append("and by the way: axis reports no error even though there is one") return errors diff --git a/tools/test-rig-loopback.yaml b/tools/test-rig-loopback.yaml index 34989214..359c3a61 100644 --- a/tools/test-rig-loopback.yaml +++ b/tools/test-rig-loopback.yaml @@ -2,7 +2,37 @@ type: loopback odrives: - - name: odrive-48V + - name: odrv-blackside + board-version: v3.4-24V + serial-number: "3061395B3235" + brake-resistance: 0.47 + uart: /dev/serial/by-id/[not-yet-used] + usb: auto + programmer: '493f6f06493f56540929113f' + vbus-voltage: 24 # [V] + max-brake-power: 150 # [W] + axes: + - name: 'M0' + motor-phase-resistance: 0.028 + motor-phase-inductance: 1.6e-05 + motor-pole-pairs: 7 + motor-direction: 1 + motor-kv: 270 + motor-max-current: 70 + motor-max-voltage: 32 + encoder-cpr: 8192 + encoder-max-rpm: 7000 + - name: 'M1' + motor-phase-resistance: 0.028 + motor-phase-inductance: 1.6e-05 + motor-pole-pairs: 7 + motor-direction: -1 + motor-kv: 270 + motor-max-current: 70 + motor-max-voltage: 32 + encoder-cpr: 8192 + encoder-max-rpm: 7000 + - name: odrv-yellowside board-version: v3.5-48V serial-number: "3660335E3037" brake-resistance: 0.47 @@ -35,4 +65,5 @@ odrives: # Mechanical couplings couplings: - - [ odrive-48V.M0, odrive-48V.M1 ] \ No newline at end of file + - [ odrv-blackside.M0, odrv-blackside.M1 ] + - [ odrv-yellowside.M0, odrv-yellowside.M1 ] \ No newline at end of file From 98106a3dc9200cdf889216c8c3378aca35be7a4c Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 21 Apr 2018 14:33:54 -0700 Subject: [PATCH 087/215] add cppstandard --- Firmware/.vscode/c_cpp_properties.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index de6eb463..92b2b983 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -37,7 +37,9 @@ "C:/Program Files (x86)/GNU Tools ARM Embedded" ], "limitSymbolsToIncludedHeaders": true - } + }, + "cStandard": "c11", + "cppStandard": "c++14" }, { "name": "Linux", From 52be9da107793eddc1c1217de675eecfc583697e Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 21 Apr 2018 15:41:33 -0700 Subject: [PATCH 088/215] wait any structure more readable --- tools/odrive/dfu.py | 4 ++-- tools/odrive/utils.py | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/tools/odrive/dfu.py b/tools/odrive/dfu.py index aad9a3d8..339d2554 100755 --- a/tools/odrive/dfu.py +++ b/tools/odrive/dfu.py @@ -13,6 +13,7 @@ import array import fractions import usb.core import odrive.discovery +from odrive.utils import Event from odrive.dfuse import * try: @@ -244,8 +245,7 @@ def launch_dfu(args, app_shutdown_token): serial_number = args.serial_number - find_odrive_cancellation_token = threading.Event() - app_shutdown_token.subscribe(lambda: find_odrive_cancellation_token.set()) + find_odrive_cancellation_token = Event(app_shutdown_token) print("Waiting for ODrive...") diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 5834ff85..efc15acb 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -9,6 +9,7 @@ import threading import platform import subprocess import os +from odrive.utils import Event try: if platform.system() == 'Windows': @@ -33,7 +34,7 @@ def start_liveplotter(get_var_callback): import matplotlib.pyplot as plt - cancellation_token = threading.Event() + cancellation_token = Event() global vals vals = [] @@ -175,7 +176,7 @@ class Event(): handler() finally: self._mutex.release() - return lambda: self.unsubscribe(handler) + return handler def unsubscribe(self, handler): self._mutex.acquire() @@ -201,16 +202,16 @@ class Event(): def wait_any(*events, timeout=None): """ Blocks until any of the specified events are triggered. - Returns the number of the event that was triggerd or raises + Returns the index of the event that was triggerd or raises a TimeoutException """ or_event = threading.Event() - unsubscribe_functions = [] + subscriptions = [] for event in events: - unsubscribe_functions.append(event.subscribe(lambda: or_event.set())) + subscriptions.append((event, event.subscribe(lambda: or_event.set()))) or_event.wait(timeout=timeout) - for unsubscribe_function in unsubscribe_functions: - unsubscribe_function() + for event, sub in subscriptions: + event.unsubscribe(sub) for i in range(len(events)): if events[i].is_set(): return i From 9af85d544eb15dd4055ebd0a3e04cb8f19d8d1ba Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 21 Apr 2018 15:53:08 -0700 Subject: [PATCH 089/215] change ODRV_FACTORY to OTP_CONFIRM --- Firmware/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firmware/Makefile b/Firmware/Makefile index d587105a..fb2a9115 100644 --- a/Firmware/Makefile +++ b/Firmware/Makefile @@ -46,7 +46,7 @@ erase_config: # FLASH_CR = (1 << FLASH_CR_PG); // unlock flash memory # [write OTP] write_otp: -ifeq ($(ODRV_FACTORY),TRUE) +ifeq ($(OTP_CONFIRM),TRUE) # Data: openocd -f interface/stlink-v2.cfg -f target/stm32f4x.cfg \ -c init \ @@ -72,7 +72,7 @@ else @echo " 1. open the Makefile and look at the write_otp target" @echo " 2. understand the structure of the OTP" @echo " 3. edit the bytes that are written to match your board version" - @echo "Run this command again, this time with ODRV_FACTORY=TRUE" + @echo "Run this command again, this time with OTP_CONFIRM=TRUE" endif clean: From 89ee84ca398c5339a10f208d49c3a1bc8873c439 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 21 Apr 2018 17:44:25 -0700 Subject: [PATCH 090/215] releae with setuptools, fix some help text --- tools/odrive/shell.py | 2 +- tools/odrive/utils.py | 1 - tools/odrivetool | 20 +++++++++++--------- tools/setup.py | 10 +++++----- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/tools/odrive/shell.py b/tools/odrive/shell.py index af2e9432..2dceeba5 100644 --- a/tools/odrive/shell.py +++ b/tools/odrive/shell.py @@ -8,7 +8,7 @@ from odrive.enums import * # pylint: disable=W0614 def print_banner(): print('Please connect your ODrive.') - print('Type help() for help.') + print('You can also type help() or quit().') def print_help(args): print('') diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index efc15acb..82eba5db 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -9,7 +9,6 @@ import threading import platform import subprocess import os -from odrive.utils import Event try: if platform.system() == 'Windows': diff --git a/tools/odrivetool b/tools/odrivetool index 2feb9b2c..8bd4b524 100755 --- a/tools/odrivetool +++ b/tools/odrivetool @@ -81,22 +81,24 @@ else: logger = Logger(verbose=args.verbose) -print("ODrive control utility v" + odrive.__version__) -if ".dev" in odrive.__version__: - print("") - logger.warn("Developer Preview") - print(" If you find issues, please report them") - print(" on https://github.com/madcowswe/ODrive/issues") - print(" or better yet, submit a pull request to fix it.") - print("") +def print_version(): + print("ODrive control utility v" + odrive.__version__) app_shutdown_token = Event() try: if args.version == True: - pass + print_version() elif args.command == 'shell': + print_version() + if ".dev" in odrive.__version__: + print("") + logger.warn("Developer Preview") + print(" If you find issues, please report them") + print(" on https://github.com/madcowswe/ODrive/issues") + print(" or better yet, submit a pull request to fix it.") + print("") import odrive.shell odrive.shell.launch_shell(args, logger, printer, app_shutdown_token) diff --git a/tools/setup.py b/tools/setup.py index 85ddd5cd..946e6900 100644 --- a/tools/setup.py +++ b/tools/setup.py @@ -10,7 +10,7 @@ To build and package the python tools into a tar archive: python setup.py sdist Warning: Before you proceed, be aware that you can upload a -specific version only ever once. After that you need to increment +specific version only once ever. After that you need to increment the hotfix number. Deleting the release manually on the PyPi website does not help. @@ -19,10 +19,10 @@ Use TestPyPi while developing. To build, package and upload the python tools to TestPyPi, run: python setup.py sdist upload -r pypitest To make a real release ensure you're at the release commit -and then run the above command without the "test". +and then run the above command without the "test" (so just "pypi"). To install a prerelease version from test index: - sudo pip install --index-url https://test.pypi.org/simple/ --no-cache-dir odrive + sudo pip install --pre --index-url https://test.pypi.org/simple/ --no-cache-dir odrive PyPi access requires that you have set up ~/.pypirc with your @@ -32,7 +32,7 @@ to publish packages with the name odrive. # TODO: add additional y/n prompt to prevent from erroneous upload -from distutils.core import setup +from setuptools import setup import os import sys @@ -77,6 +77,7 @@ setup( url = 'https://github.com/madcowswe/ODrive', keywords = ['odrive', 'motor', 'motor control'], install_requires = [ + 'ipython', # Used to do the interactive parts of the odrivetool 'PyUSB', # Required to access USB devices from Python through libusb 'PySerial', # Required to access serial devices from Python 'IntelHex', # Used to by DFU to load firmware files @@ -84,7 +85,6 @@ setup( 'pywin32==222;platform_system=="Windows"' # Required for fancy terminal features on Windows ], package_data={'': ['version.txt']}, - include_package_data=True, classifiers = [], ) From 9af3a7135445009e0c27fba4caec6a5de1986be8 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 21 Apr 2018 17:45:42 -0700 Subject: [PATCH 091/215] Update CHANGELOG.md --- Firmware/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/CHANGELOG.md b/Firmware/CHANGELOG.md index 8d3d8b27..4f1c94ca 100644 --- a/Firmware/CHANGELOG.md +++ b/Firmware/CHANGELOG.md @@ -2,7 +2,7 @@ Please add a note of your changes below this heading if you make a Pull Request. ### Added - * `make write_otp` command to burn the board version onto the ODrive's one-time programmable memory. If you have an ODrive v3.4 or older, you should run this once for a better firmware update user experience in the future. Run the command without any options for more details. Once set, the board version is exposed through the `board_version_[...]` properties. + * `make write_otp` command to burn the board version onto the ODrive's one-time programmable memory. If you have an ODrive v3.4 or older, you should run this once for a better firmware update user experience in the future. Run the command without any options for more details. Once set, the board version is exposed through the `hw_version_[...]` properties. * bake Git-derived firmware version into firmware binary. The firmware version is exposed through the `fw_version_[...]` properties. * infrastructure to publish the python tools to PyPi. See `tools/setup.py` for details. From c0405e051a95dbda3b2370ff2a4ad0052f12a407 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 13 Apr 2018 17:17:24 -0700 Subject: [PATCH 092/215] split plotting behaviour across windows and notwindows --- tools/odrive/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 82eba5db..16f47fcf 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -67,8 +67,10 @@ def start_liveplotter(get_var_callback): while not cancellation_token.is_set(): plt.clf() plt.plot(vals) - #time.sleep(1/plot_rate) - fig.canvas.flush_events() + if platform.system() == "Windows": + plt.pause(1/plot_rate) + else: + fig.canvas.flush_events() threading.Thread(target=fetch_data).start() threading.Thread(target=plot_data).start() From 57acc2e26180f2e16fd273e6941c5e070f851a2d Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 21 Apr 2018 17:51:18 -0700 Subject: [PATCH 093/215] add cppstandard --- Firmware/.vscode/c_cpp_properties.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index de6eb463..92b2b983 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -37,7 +37,9 @@ "C:/Program Files (x86)/GNU Tools ARM Embedded" ], "limitSymbolsToIncludedHeaders": true - } + }, + "cStandard": "c11", + "cppStandard": "c++14" }, { "name": "Linux", From 9d884e59703cb3848425e1fd3014397dcca557c7 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 21 Apr 2018 18:17:50 -0700 Subject: [PATCH 094/215] Update CHANGELOG.md --- Firmware/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/CHANGELOG.md b/Firmware/CHANGELOG.md index 7b959fca..8131e200 100644 --- a/Firmware/CHANGELOG.md +++ b/Firmware/CHANGELOG.md @@ -25,6 +25,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * Most of the code from `lowlevel.c` moved to `axis.cpp`, `encoder.cpp`, `controller.cpp`, `sensorless_estimator.cpp`, `motor.cpp` and the corresponding header files * Refactoring of the developer-facing communication protocol interface. See e.g. `axis.hpp` or `controller.hpp` for examples on how to add your own fields and functions * Change of the user-facing field paths. E.g. `my_odrive.motor0.pos_setpoint` is now at `my_odrive.axis0.controller.pos_setpoint`. Names are mostly unchanged. +* Rewrite of the top-level per-axis state-machine * The build is now configured using the `tup.config` file instead of editing source files. Make sure you set your board version correctly. See [here](README.md#configuring-the-build) for details. * The toplevel directory for tup is now `Firmware`. If you used tup before, go to `Firmware` and run `rm -rd ../.tup; rm -rd build/*; make`. * Update CubeMX generated STM platform code to version 1.19.0 From 9e62f083499f09326425967753a71678493c5174 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 21 Apr 2018 19:29:21 -0700 Subject: [PATCH 095/215] update Changelog --- Firmware/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/CHANGELOG.md b/Firmware/CHANGELOG.md index 2774a8f1..856c9d15 100644 --- a/Firmware/CHANGELOG.md +++ b/Firmware/CHANGELOG.md @@ -2,6 +2,7 @@ Please add a note of your changes below this heading if you make a Pull Request. ### Added +* Encoder can now go forever in velocity/torque mode due to using circular encoder space. * `make write_otp` command to burn the board version onto the ODrive's one-time programmable memory. If you have an ODrive v3.4 or older, you should run this once for a better firmware update user experience in the future. Run the command without any options for more details. Once set, the board version is exposed through the `board_version_[...]` properties. * bake Git-derived firmware version into firmware binary. The firmware version is exposed through the `fw_version_[...]` properties. * infrastructure to publish the python tools to PyPi. See `tools/setup.py` for details. From 0bf5debdcab368107d2a1d6801a5beaec94930c1 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 21 Apr 2018 19:33:39 -0700 Subject: [PATCH 096/215] GPIO_3 input by default and EXTI2 disabled by default --- Firmware/Board/v3/Inc/main.h | 1 - Firmware/Board/v3/Odrive.ioc | 8 ++------ Firmware/Board/v3/Src/gpio.c | 15 ++------------- 3 files changed, 4 insertions(+), 20 deletions(-) diff --git a/Firmware/Board/v3/Inc/main.h b/Firmware/Board/v3/Inc/main.h index 1ff706df..cef4368a 100644 --- a/Firmware/Board/v3/Inc/main.h +++ b/Firmware/Board/v3/Inc/main.h @@ -93,7 +93,6 @@ #define GPIO_2_GPIO_Port GPIOA #define GPIO_3_Pin GPIO_PIN_2 #define GPIO_3_GPIO_Port GPIOA -#define GPIO_3_EXTI_IRQn EXTI2_IRQn #define GPIO_4_Pin GPIO_PIN_3 #define GPIO_4_GPIO_Port GPIOA #define M1_TEMP_Pin GPIO_PIN_4 diff --git a/Firmware/Board/v3/Odrive.ioc b/Firmware/Board/v3/Odrive.ioc index f13657b1..70c238a4 100644 --- a/Firmware/Board/v3/Odrive.ioc +++ b/Firmware/Board/v3/Odrive.ioc @@ -199,7 +199,6 @@ NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:true NVIC.DMA1_Stream2_IRQn=true\:5\:0\:false\:false\:true\:true\:true NVIC.DMA1_Stream4_IRQn=true\:5\:0\:false\:false\:true\:true\:false NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:true -NVIC.EXTI2_IRQn=true\:0\:0\:false\:false\:false\:false\:true NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:true NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:true NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:true @@ -241,11 +240,10 @@ PA15.GPIOParameters=GPIO_Label PA15.GPIO_Label=GPIO_7 PA15.Locked=true PA15.Signal=GPIO_Input -PA2.GPIOParameters=GPIO_PuPd,GPIO_Label +PA2.GPIOParameters=GPIO_Label PA2.GPIO_Label=GPIO_3 -PA2.GPIO_PuPd=GPIO_PULLDOWN PA2.Locked=true -PA2.Signal=GPXTI2 +PA2.Signal=GPIO_Input PA3.GPIOParameters=GPIO_PuPd,GPIO_Label PA3.GPIO_Label=GPIO_4 PA3.GPIO_PuPd=GPIO_NOPULL @@ -497,8 +495,6 @@ SH.ADCx_IN5.ConfNb=2 SH.ADCx_IN6.0=ADC1_IN6,IN6 SH.ADCx_IN6.1=ADC2_IN6,IN6 SH.ADCx_IN6.ConfNb=2 -SH.GPXTI2.0=GPIO_EXTI2 -SH.GPXTI2.ConfNb=1 SH.S_TIM1_CH1.0=TIM1_CH1,PWM Generation1 CH1 CH1N SH.S_TIM1_CH1.ConfNb=1 SH.S_TIM1_CH2.0=TIM1_CH2,PWM Generation2 CH2 CH2N diff --git a/Firmware/Board/v3/Src/gpio.c b/Firmware/Board/v3/Src/gpio.c index fb163f71..85b9028f 100644 --- a/Firmware/Board/v3/Src/gpio.c +++ b/Firmware/Board/v3/Src/gpio.c @@ -106,14 +106,8 @@ void MX_GPIO_Init(void) GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); - /*Configure GPIO pin : PtPin */ - GPIO_InitStruct.Pin = GPIO_3_Pin; - GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING; - GPIO_InitStruct.Pull = GPIO_PULLDOWN; - HAL_GPIO_Init(GPIO_3_GPIO_Port, &GPIO_InitStruct); - - /*Configure GPIO pins : PAPin PAPin */ - GPIO_InitStruct.Pin = GPIO_4_Pin|GPIO_7_Pin; + /*Configure GPIO pins : PAPin PAPin PAPin */ + GPIO_InitStruct.Pin = GPIO_3_Pin|GPIO_4_Pin|GPIO_7_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); @@ -137,11 +131,6 @@ void MX_GPIO_Init(void) GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(nFAULT_GPIO_Port, &GPIO_InitStruct); - /* EXTI interrupt init*/ - // TODO get Cube to not emit this - // HAL_NVIC_SetPriority(EXTI2_IRQn, 0, 0); - // HAL_NVIC_EnableIRQ(EXTI2_IRQn); - } /* USER CODE BEGIN 2 */ From 89a3d72b4b34939072a398d77caacc415b4d4347 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 21 Apr 2018 19:44:15 -0700 Subject: [PATCH 097/215] update changelog --- Firmware/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Firmware/CHANGELOG.md b/Firmware/CHANGELOG.md index 856c9d15..afe587f0 100644 --- a/Firmware/CHANGELOG.md +++ b/Firmware/CHANGELOG.md @@ -19,6 +19,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * (experimental: start liveplotter from `odrivetool` shell by typing `start_liveplotter(lambda: odrv0.motor0.encoder.encoder_state)`) * Set thread priority of USB pump thread above protocol thread +* GPIO3 not sensitive to edges by default ### Fixed * Enums now transported with correct underlying type on native protocol From 80b3bab5530d0930fdd4974f264edef803f23047 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sat, 21 Apr 2018 20:09:24 -0700 Subject: [PATCH 098/215] cancel receiver thread correctly on app shutdown --- tools/odrive/dfu.py | 2 +- tools/odrive/discovery.py | 13 ++++++++----- tools/odrive/protocol.py | 10 +++++----- tools/odrive/serial_transport.py | 6 ++++-- tools/odrive/shell.py | 1 + tools/odrive/usbbulk_transport.py | 5 +++-- tools/odrive/utils.py | 7 +++++-- tools/odrivetool | 18 ++++++++++++------ 8 files changed, 39 insertions(+), 23 deletions(-) diff --git a/tools/odrive/dfu.py b/tools/odrive/dfu.py index 339d2554..d1b59f82 100755 --- a/tools/odrive/dfu.py +++ b/tools/odrive/dfu.py @@ -251,7 +251,7 @@ def launch_dfu(args, app_shutdown_token): # Scan for ODrives not in DFU mode and put them into DFU mode once they appear # We only scan on USB because DFU is only possible over USB - odrive.discovery.find_all(args.path, serial_number, put_odrive_into_dfu_mode, find_odrive_cancellation_token) + odrive.discovery.find_all(args.path, serial_number, put_odrive_into_dfu_mode, find_odrive_cancellation_token, app_shutdown_token) # Poll libUSB until a device in DFU mode is found while not app_shutdown_token.is_set(): diff --git a/tools/odrive/discovery.py b/tools/odrive/discovery.py index fe4a37f0..a6536638 100644 --- a/tools/odrive/discovery.py +++ b/tools/odrive/discovery.py @@ -24,7 +24,8 @@ def noprint(text): def find_all(path, serial_number, did_discover_object_callback, - cancellation_token, printer=noprint): + search_cancellation_token, + channel_termination_token, printer=noprint): """ Starts scanning for ODrives that match the specified path spec and calls the callback for each ODrive that is found. @@ -75,21 +76,23 @@ def find_all(path, serial_number, the_rest = ':'.join(search_spec.split(':')[1:]) if prefix in channel_types: threading.Thread(target=channel_types[prefix], - args=(the_rest, serial_number, did_discover_channel, cancellation_token, printer)).start() + args=(the_rest, serial_number, did_discover_channel, search_cancellation_token, channel_termination_token, printer)).start() else: raise Exception("Invalid path spec \"{}\"".format(search_spec)) -def find_any(path="usb", serial_number=None, cancellation_token=None, timeout=None, printer=noprint): +def find_any(path="usb", serial_number=None, + search_cancellation_token=None, channel_termination_token=None, + timeout=None, printer=noprint): """ Blocks until the first matching ODrive is connected and then returns that device """ result = [ None ] - done_signal = Event(cancellation_token) + done_signal = Event(search_cancellation_token) def did_discover_object(obj): result[0] = obj done_signal.set() - find_all(path, serial_number, did_discover_object, done_signal, printer) + find_all(path, serial_number, did_discover_object, done_signal, channel_termination_token, printer) try: done_signal.wait(timeout=timeout) finally: diff --git a/tools/odrive/protocol.py b/tools/odrive/protocol.py index 566e1802..9ea1dd88 100644 --- a/tools/odrive/protocol.py +++ b/tools/odrive/protocol.py @@ -206,7 +206,7 @@ class Channel(PacketSink): _resend_timeout = 0.1 # [s] _send_attempts = 5 - def __init__(self, name, input, output, printer): + def __init__(self, name, input, output, cancellation_token, printer): """ Params: input: A PacketSource where this channel will source packets from on @@ -223,8 +223,8 @@ class Channel(PacketSink): self._expected_acks = {} self._responses = {} self._my_lock = threading.Lock() - self._channel_broken = Event() - self.start_receiver_thread(Event()) # TODO: use app_shutdown_token + self._channel_broken = Event(cancellation_token) + self.start_receiver_thread(Event(self._channel_broken)) # TODO: use app_shutdown_token def start_receiver_thread(self, cancellation_token): """ @@ -246,7 +246,7 @@ class Channel(PacketSink): # Process response # This should not throw an exception, otherwise the channel breaks self.process_packet(response) - print("receiver thread is exiting") + #print("receiver thread is exiting") except Exception: self._printer("receiver thread is exiting: " + traceback.format_exc()) finally: @@ -296,7 +296,7 @@ class Channel(PacketSink): self._my_lock.release() # Wait for ACK until the resend timeout is exceeded try: - if wait_any(ack_event, self._channel_broken, timeout=self._resend_timeout) != 0: + if wait_any(self._resend_timeout, ack_event, self._channel_broken) != 0: raise ChannelBrokenException() except odrive.utils.TimeoutException: attempt += 1 diff --git a/tools/odrive/serial_transport.py b/tools/odrive/serial_transport.py index dd595c6d..8a8b350a 100644 --- a/tools/odrive/serial_transport.py +++ b/tools/odrive/serial_transport.py @@ -6,6 +6,7 @@ PacketSource/PacketSink interfaces for serial ports. import os import re import time +import traceback import serial import serial.tools.list_ports import odrive.protocol @@ -53,10 +54,11 @@ def find_pyserial_ports(): return [x.device for x in serial.tools.list_ports.comports()] -def discover_channels(path, serial_number, callback, cancellation_token, printer): +def discover_channels(path, serial_number, callback, cancellation_token, channel_termination_token, printer): """ Scans for serial ports that match the path spec. This function blocks until cancellation_token is set. + Channels spawned by this function run until channel_termination_token is set. """ if path == None: # This regex should match all desired port names on macOS, @@ -86,7 +88,7 @@ def discover_channels(path, serial_number, callback, cancellation_token, printer output_stream = odrive.protocol.PacketToStreamConverter(serial_device) channel = odrive.protocol.Channel( "serial port {}@{}".format(port_name, ODRIVE_BAUDRATE), - input_stream, output_stream, printer) + input_stream, output_stream, channel_termination_token, printer) channel.serial_device = serial_device except serial.serialutil.SerialException: printer("Serial device init failed. Ignoring this port. More info: " + traceback.format_exc()) diff --git a/tools/odrive/shell.py b/tools/odrive/shell.py index 2dceeba5..8c441097 100644 --- a/tools/odrive/shell.py +++ b/tools/odrive/shell.py @@ -78,6 +78,7 @@ def launch_shell(args, logger, printer, app_shutdown_token): odrive.discovery.find_all(args.path, args.serial_number, lambda dev: did_discover_device(dev, logger, app_shutdown_token), app_shutdown_token, + app_shutdown_token, printer=printer) # Check if IPython is installed diff --git a/tools/odrive/usbbulk_transport.py b/tools/odrive/usbbulk_transport.py index fbfcfcee..2ed8ba11 100644 --- a/tools/odrive/usbbulk_transport.py +++ b/tools/odrive/usbbulk_transport.py @@ -139,10 +139,11 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) return 64 -def discover_channels(path, serial_number, callback, cancellation_token, printer): +def discover_channels(path, serial_number, callback, cancellation_token, channel_termination_token, printer): """ Scans for USB devices that match the path spec. This function blocks until cancellation_token is set. + Channels spawned by this function run until channel_termination_token is set. """ if path == None or path == "": bus = None @@ -181,7 +182,7 @@ def discover_channels(path, serial_number, callback, cancellation_token, printer bulk_device.init() channel = odrive.protocol.Channel( "USB device bus {} device {}".format(usb_device.bus, usb_device.address), - bulk_device, bulk_device, printer) + bulk_device, bulk_device, channel_termination_token, printer) channel.usb_device = usb_device # for debugging only except usb.core.USBError as ex: if ex.errno == 13: diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 16f47fcf..6d8c8cc1 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -144,7 +144,7 @@ class Event(): self._subscribers = [] self._mutex = threading.Lock() if not trigger is None: - trigger.subscribe(self.set()) + trigger.subscribe(lambda: self.set()) def is_set(self): return self._evt.is_set() @@ -170,6 +170,8 @@ class Event(): handler is invoked immediately. Returns a function that can be invoked to unsubscribe. """ + if handler is None: + raise TypeError self._mutex.acquire() try: self._subscribers.append(handler) @@ -200,11 +202,12 @@ class Event(): self.set() threading.Thread(target=delayed_trigger, daemon=True).start() -def wait_any(*events, timeout=None): +def wait_any(timeout=None, *events): """ Blocks until any of the specified events are triggered. Returns the index of the event that was triggerd or raises a TimeoutException + Param timeout: A timeout in seconds """ or_event = threading.Event() subscriptions = [] diff --git a/tools/odrivetool b/tools/odrivetool index 8bd4b524..a455df08 100755 --- a/tools/odrivetool +++ b/tools/odrivetool @@ -3,13 +3,22 @@ ODrive command line utility """ +from __future__ import print_function +import sys import argparse import odrive.discovery from odrive.utils import Logger, Event # Flush stdout by default -import functools -print = functools.partial(print, flush=True) +# Source: +# https://stackoverflow.com/questions/230751/how-to-flush-output-of-python-print +old_print = print +def print(*args, **kwargs): + kwargs.pop('flush', False) + old_print(*args, **kwargs) + file = kwargs.get('file', sys.stdout) + # Why might file=None? IDK, but it works for print(i, file=None) + file.flush() if file is not None else sys.stdout.flush() ## Parse arguments ## @@ -69,10 +78,6 @@ if args.command is None: args.command = 'shell' args.no_ipython = False -# We are interactively printing status messages, so flush by default -import functools -print = functools.partial(print, flush=True) - # TODO: deprecate printer - use logger instead if (args.verbose): printer = print @@ -103,6 +108,7 @@ try: odrive.shell.launch_shell(args, logger, printer, app_shutdown_token) elif args.command == 'dfu': + print_version() import odrive.dfu odrive.dfu.launch_dfu(args, app_shutdown_token) From f6265404942d58d2e54e1328320ad141eab02c49 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sat, 21 Apr 2018 20:19:21 -0700 Subject: [PATCH 099/215] remove unused functions --- tools/odrive/usbbulk_transport.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tools/odrive/usbbulk_transport.py b/tools/odrive/usbbulk_transport.py index 2ed8ba11..5a3bd772 100644 --- a/tools/odrive/usbbulk_transport.py +++ b/tools/odrive/usbbulk_transport.py @@ -132,12 +132,6 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) self._was_damaged = True raise odrive.protocol.ChannelDamagedException() - def send_max(self): - return 64 - - def receive_max(self): - return 64 - def discover_channels(path, serial_number, callback, cancellation_token, channel_termination_token, printer): """ From b1f9be531d04672047cadd15bbf0585af450367d Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sat, 21 Apr 2018 20:22:30 -0700 Subject: [PATCH 100/215] request more bytes at a time when reading JSON --- tools/odrive/protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/odrive/protocol.py b/tools/odrive/protocol.py index 9ea1dd88..d8c49f3a 100644 --- a/tools/odrive/protocol.py +++ b/tools/odrive/protocol.py @@ -319,7 +319,7 @@ class Channel(PacketSink): # TODO: handle device that could (maliciously) send infinite stream buffer = bytes() while True: - chunk_length = 64 + chunk_length = 512 chunk = self.remote_endpoint_operation(endpoint_id, struct.pack(" Date: Sat, 21 Apr 2018 20:23:54 -0700 Subject: [PATCH 101/215] Update protocol.py --- tools/odrive/protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/odrive/protocol.py b/tools/odrive/protocol.py index d8c49f3a..4a4b3012 100644 --- a/tools/odrive/protocol.py +++ b/tools/odrive/protocol.py @@ -224,7 +224,7 @@ class Channel(PacketSink): self._responses = {} self._my_lock = threading.Lock() self._channel_broken = Event(cancellation_token) - self.start_receiver_thread(Event(self._channel_broken)) # TODO: use app_shutdown_token + self.start_receiver_thread(Event(self._channel_broken)) def start_receiver_thread(self, cancellation_token): """ From e581cc9c90ba4db9be7ac539895d234bea596519 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sat, 21 Apr 2018 14:21:29 -0700 Subject: [PATCH 102/215] move enable_dfu_mode() to main.cpp --- Firmware/MotorControl/main.cpp | 6 ++++++ Firmware/MotorControl/odrive_main.h | 1 + Firmware/communication/communication.cpp | 6 ------ 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 6cf7ae62..b7ec03d5 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -53,6 +53,12 @@ void erase_configuration(void) { NVM_erase(); } +void enter_dfu_mode(void) { + __asm volatile ("CPSID I\n\t":::"memory"); // disable interrupts + _reboot_cookie = 0xDEADBEEF; + NVIC_SystemReset(); +} + extern "C" { int odrive_main(void); void vApplicationStackOverflowHook(void) { for(;;); } diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index ed28b832..a66fcb74 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -85,5 +85,6 @@ inline ENUMTYPE operator ~ (ENUMTYPE a) { return static_cast(~static_c // general system functions defined in main.cpp void save_configuration(void); void erase_configuration(void); +void enter_dfu_mode(void); #endif /* __ODRIVE_MAIN_H */ diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 37f4385b..27e08bfb 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -65,12 +65,6 @@ const uint8_t fw_version_unreleased = FW_VERSION_UNRELEASED; // 0 for official r /* Private function prototypes -----------------------------------------------*/ /* Function implementations --------------------------------------------------*/ -void enter_dfu_mode() { - __asm volatile ("CPSID I\n\t":::"memory"); // disable interrupts - _reboot_cookie = 0xDEADBEEF; - NVIC_SystemReset(); -} - void init_communication(void) { printf("hi!\r\n"); From 4cefbbb0f9224fbb8a0c474b2048c289177c9096 Mon Sep 17 00:00:00 2001 From: samuelsadok Date: Sat, 21 Apr 2018 22:49:18 -0700 Subject: [PATCH 103/215] dont start thread with daemon=True, doesnt work in python2 --- tools/odrive/protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/odrive/protocol.py b/tools/odrive/protocol.py index 4a4b3012..42d96e7f 100644 --- a/tools/odrive/protocol.py +++ b/tools/odrive/protocol.py @@ -251,7 +251,7 @@ class Channel(PacketSink): self._printer("receiver thread is exiting: " + traceback.format_exc()) finally: self._channel_broken.set() - threading.Thread(target=receiver_thread, daemon=True).start() + threading.Thread(target=receiver_thread).start() def remote_endpoint_operation(self, endpoint_id, input, expect_ack, output_length): if input is None: From 3d13da4e921d9d34e6bacb4303f24de19c3012d9 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sun, 22 Apr 2018 00:57:14 -0700 Subject: [PATCH 104/215] implement function return values on protocol (this time for real) --- Firmware/communication/communication.cpp | 4 +- Firmware/communication/protocol.hpp | 170 ++++++++++------------- 2 files changed, 76 insertions(+), 98 deletions(-) diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 8e4f826a..ed2c1d0d 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -92,6 +92,7 @@ public: void NVIC_SystemReset_helper() { NVIC_SystemReset(); } void enter_dfu_mode_helper() { enter_dfu_mode(); } float get_oscilloscope_val(uint32_t index) { return oscilloscope[index]; } + int32_t test_function(int32_t delta) { static int cnt = 0; return cnt += delta; } } static_functions; // When adding new functions/variables to the protocol, be careful not to @@ -120,7 +121,8 @@ static inline auto make_obj_tree() { ), make_protocol_object("axis0", axes[0]->make_protocol_definitions()), make_protocol_object("axis1", axes[1]->make_protocol_definitions()), - make_protocol_function_with_ret("get_oscilloscope_val", static_functions, &StaticFunctions::get_oscilloscope_val, "index"), + make_protocol_function("get_oscilloscope_val", static_functions, &StaticFunctions::get_oscilloscope_val, "index"), + make_protocol_function("test_function", static_functions, &StaticFunctions::test_function, "delta"), make_protocol_function("save_configuration", static_functions, &StaticFunctions::save_configuration_helper), make_protocol_function("erase_configuration", static_functions, &StaticFunctions::erase_configuration_helper), make_protocol_function("reboot", static_functions, &StaticFunctions::NVIC_SystemReset_helper), diff --git a/Firmware/communication/protocol.hpp b/Firmware/communication/protocol.hpp index 827b226a..0bafbcb5 100644 --- a/Firmware/communication/protocol.hpp +++ b/Firmware/communication/protocol.hpp @@ -775,89 +775,50 @@ struct PropertyListFactory { }; -template -class ProtocolFunction : public Endpoint { +template +struct return_type; + +template<> +struct return_type<> { typedef void type; }; +template +struct return_type { typedef T type; }; +template +struct return_type { typedef std::tuple type; }; + + + +template +class ProtocolFunction; + +template + //template typename asd, + //template typename ssss> +class ProtocolFunction, std::tuple> : Endpoint { public: - static constexpr size_t endpoint_count = 1 + MemberList...>::endpoint_count; - template - ProtocolFunction(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TArgs...), TNames ... names) : - name_(name), all_arg_names_{names...}, obj_(obj), func_ptr_(func_ptr), - input_properties_(PropertyListFactory::template make_property_list<0>(all_arg_names_, in_args_)) + + // @brief The return type of the function as written by a C++ programmer + using TRet = typename return_type::type; + + static constexpr size_t endpoint_count = 1 + MemberList...>::endpoint_count + MemberList...>::endpoint_count; + + ProtocolFunction(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TInputs...), + std::array input_names, + std::array output_names) : + name_(name), obj_(obj), func_ptr_(func_ptr), + input_names_{input_names}, output_names_{output_names}, + input_properties_(PropertyListFactory::template make_property_list<0>(input_names_, in_args_)), + output_properties_(PropertyListFactory::template make_property_list<0>(output_names_, out_args_)) { LOG_PROTO("my tuple is at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); } ProtocolFunction(const ProtocolFunction& other) : - name_(other.name_), all_arg_names_(other.all_arg_names_), obj_(other.obj_), func_ptr_(other.func_ptr_), - input_properties_(PropertyListFactory::template make_property_list<0>( - all_arg_names_, in_args_)) - { - LOG_PROTO("COPIED! my tuple is at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); - } - - void write_json(size_t id, StreamSink* output) { - // write name - write_string("{\"name\":\"", output); - write_string(name_, output); - - // write endpoint ID - write_string("\",\"id\":", output); - char id_buf[10]; - snprintf(id_buf, sizeof(id_buf), "%u", id); // TODO: get rid of printf - write_string(id_buf, output); - - // write arguments - write_string(",\"type\":\"function\",\"arguments\":[", output); - input_properties_.write_json(id + 1, output), - write_string("]}", output); - } - - Endpoint* get_by_name(const char * name, size_t length) { - return nullptr; // can't address functions by name - } - - void register_endpoints(Endpoint** list, size_t id, size_t length) { - if (id < length) - list[id] = this; - input_properties_.register_endpoints(list, id + 1, length); - } - - void handle(const uint8_t* input, size_t input_length, StreamSink* output) { - (void) input; - (void) input_length; - (void) output; - LOG_PROTO("tuple still at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); - LOG_PROTO("invoke function using %d and %.3f\r\n", std::get<0>(in_args_), std::get<1>(in_args_)); - invoke_function_with_tuple(obj_, func_ptr_, in_args_); - } - - const char * name_; - std::array all_arg_names_; // TODO: remove - TObj& obj_; - TRet(TObj::*func_ptr_)(TArgs...); - std::tuple in_args_; - MemberList...> input_properties_; -}; - -template -class ProtocolFunctionWithRet : Endpoint { -public: - static constexpr size_t endpoint_count = 1 + MemberList>::endpoint_count + MemberList...>::endpoint_count; - template - ProtocolFunctionWithRet(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TArgs...), TNames ... names) : - name_(name), out_arg_names_{"out"}, all_arg_names_{names...}, obj_(obj), func_ptr_(func_ptr), - output_properties_(PropertyListFactory::template make_property_list<0>(out_arg_names_, out_args_)), - input_properties_(PropertyListFactory::template make_property_list<0>(all_arg_names_, in_args_)) - { - LOG_PROTO("my tuple is at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); - } - - ProtocolFunctionWithRet(const ProtocolFunctionWithRet& other) : - name_(other.name_), all_arg_names_(other.all_arg_names_), obj_(other.obj_), func_ptr_(other.func_ptr_), - output_properties_(PropertyListFactory::template make_property_list<0>( - out_arg_names_, out_args_)), - input_properties_(PropertyListFactory::template make_property_list<0>( - all_arg_names_, in_args_)) + name_(other.name_), obj_(other.obj_), func_ptr_(other.func_ptr_), + input_names_(other.input_names_), output_names_(other.output_names_), + input_properties_(PropertyListFactory::template make_property_list<0>( + input_names_, in_args_)), + output_properties_(PropertyListFactory::template make_property_list<0>( + output_names_, out_args_)) { LOG_PROTO("COPIED! my tuple is at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); } @@ -881,6 +842,10 @@ public: write_string("]}", output); } + Endpoint* get_by_name(const char * name, size_t length) { + return nullptr; // can't address functions by name + } + void register_endpoints(Endpoint** list, size_t id, size_t length) { if (id < length) list[id] = this; @@ -888,40 +853,51 @@ public: output_properties_.register_endpoints(list, id + 1 + decltype(input_properties_)::endpoint_count, length); } + template std::enable_if_t + handle_ex() { + invoke_function_with_tuple(obj_, func_ptr_, in_args_); + } + + template std::enable_if_t + handle_ex() { + std::get<0>(out_args_) = invoke_function_with_tuple(obj_, func_ptr_, in_args_); + } + + template std::enable_if_t= 2> + handle_ex() { + out_args_ = invoke_function_with_tuple(obj_, func_ptr_, in_args_); + } + void handle(const uint8_t* input, size_t input_length, StreamSink* output) { (void) input; (void) input_length; (void) output; LOG_PROTO("tuple still at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); LOG_PROTO("invoke function using %d and %.3f\r\n", std::get<0>(in_args_), std::get<1>(in_args_)); - std::get<0>(out_args_) = invoke_function_with_tuple(obj_, func_ptr_, in_args_); + handle_ex(); } const char * name_; - std::array out_arg_names_; // TODO: remove - std::array all_arg_names_; // TODO: remove TObj& obj_; - TRet(TObj::*func_ptr_)(TArgs...); - //TRet ret_val_; - std::tuple out_args_; - std::tuple in_args_; - MemberList> output_properties_; - MemberList...> input_properties_; + TRet(TObj::*func_ptr_)(TInputs...); + std::array input_names_; // TODO: remove + std::array output_names_; // TODO: remove + std::tuple in_args_; + std::tuple out_args_; + MemberList...> input_properties_; + MemberList...> output_properties_; }; -//template> -//ProtocolFunction make_protocol_function(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TArgs...), TNames ... names) { -// return ProtocolFunction(name, obj, func_ptr, names...); -//} - -template> -ProtocolFunction make_protocol_function(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TArgs...), TNames ... names) { - return ProtocolFunction(name, obj, func_ptr, names...); +template> +ProtocolFunction, std::tuple<>> make_protocol_function(const char * name, TObj& obj, void(TObj::*func_ptr)(TArgs...), TNames ... names) { + return ProtocolFunction, std::tuple<>>(name, obj, func_ptr, {names...}, {}); } -template> -ProtocolFunctionWithRet make_protocol_function_with_ret(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TArgs...), TNames ... names) { - return ProtocolFunctionWithRet(name, obj, func_ptr, names...); +template::value>> +ProtocolFunction, std::tuple> make_protocol_function(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TArgs...), TNames ... names) { + return ProtocolFunction, std::tuple>(name, obj, func_ptr, {names...}, {"result"}); } From d190ecddf5b0f1c370fd095a40a41b39471211a9 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sun, 22 Apr 2018 01:04:15 -0700 Subject: [PATCH 105/215] remove explicit copy constructor that doesn't do anything --- Firmware/communication/protocol.hpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Firmware/communication/protocol.hpp b/Firmware/communication/protocol.hpp index 0bafbcb5..ea4f2c65 100644 --- a/Firmware/communication/protocol.hpp +++ b/Firmware/communication/protocol.hpp @@ -804,7 +804,7 @@ public: ProtocolFunction(const char * name, TObj& obj, TRet(TObj::*func_ptr)(TInputs...), std::array input_names, std::array output_names) : - name_(name), obj_(obj), func_ptr_(func_ptr), + name_(name), obj_(&obj), func_ptr_(func_ptr), input_names_{input_names}, output_names_{output_names}, input_properties_(PropertyListFactory::template make_property_list<0>(input_names_, in_args_)), output_properties_(PropertyListFactory::template make_property_list<0>(output_names_, out_args_)) @@ -855,17 +855,17 @@ public: template std::enable_if_t handle_ex() { - invoke_function_with_tuple(obj_, func_ptr_, in_args_); + invoke_function_with_tuple(*obj_, func_ptr_, in_args_); } template std::enable_if_t handle_ex() { - std::get<0>(out_args_) = invoke_function_with_tuple(obj_, func_ptr_, in_args_); + std::get<0>(out_args_) = invoke_function_with_tuple(*obj_, func_ptr_, in_args_); } template std::enable_if_t= 2> handle_ex() { - out_args_ = invoke_function_with_tuple(obj_, func_ptr_, in_args_); + out_args_ = invoke_function_with_tuple(*obj_, func_ptr_, in_args_); } void handle(const uint8_t* input, size_t input_length, StreamSink* output) { @@ -878,7 +878,7 @@ public: } const char * name_; - TObj& obj_; + TObj* obj_; TRet(TObj::*func_ptr_)(TInputs...); std::array input_names_; // TODO: remove std::array output_names_; // TODO: remove From cdeca6680a2aba2ba91ad30a8b18903a4d448217 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sun, 22 Apr 2018 16:30:59 -0700 Subject: [PATCH 106/215] add retry in wait_while_state in dfuse --- tools/odrive/dfuse/DfuDevice.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/odrive/dfuse/DfuDevice.py b/tools/odrive/dfuse/DfuDevice.py index dc5ac152..b9ca449c 100644 --- a/tools/odrive/dfuse/DfuDevice.py +++ b/tools/odrive/dfuse/DfuDevice.py @@ -83,7 +83,11 @@ class DfuDevice: else: states = state - status = self.get_status() + try: + status = self.get_status() + except: + time.sleep(0.100) + status = self.get_status() while (status[1] in states): claimed_timeout = status[2] From 3a19ce4b73cb0c1c4765b6a84ef22d1b25172083 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 23 Apr 2018 13:25:25 -0700 Subject: [PATCH 107/215] remove explicit copy constructor that doesn't do anything --- Firmware/communication/protocol.hpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Firmware/communication/protocol.hpp b/Firmware/communication/protocol.hpp index ea4f2c65..87ef82cc 100644 --- a/Firmware/communication/protocol.hpp +++ b/Firmware/communication/protocol.hpp @@ -812,17 +812,6 @@ public: LOG_PROTO("my tuple is at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); } - ProtocolFunction(const ProtocolFunction& other) : - name_(other.name_), obj_(other.obj_), func_ptr_(other.func_ptr_), - input_names_(other.input_names_), output_names_(other.output_names_), - input_properties_(PropertyListFactory::template make_property_list<0>( - input_names_, in_args_)), - output_properties_(PropertyListFactory::template make_property_list<0>( - output_names_, out_args_)) - { - LOG_PROTO("COPIED! my tuple is at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); - } - void write_json(size_t id, StreamSink* output) { // write name write_string("{\"name\":\"", output); From 1734f86ea5be80ccdd51b387ac87812856c35f0f Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 23 Apr 2018 14:31:07 -0700 Subject: [PATCH 108/215] catch brake resistor deadtime violations --- Firmware/MotorControl/low_level.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index d7cfcc86..f84b90fc 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -182,6 +182,8 @@ void safety_critical_disarm_brake_resistor() { // @brief Updates the brake resistor PWM timings unless // the brake resistor is disarmed. void safety_critical_apply_brake_resistor_timings(uint32_t low_off, uint32_t high_on) { + if (high_on - low_off > TIM_APB1_DEADTIME_CLOCKS) + for(;;); uint8_t sr = cpu_enter_critical(); if (brake_resistor_armed_) { // Safe update of low and high side timings @@ -431,7 +433,7 @@ void update_brake_current() { float brake_duty = brake_current * board_config.brake_resistance / vbus_voltage; // Duty limit at 90% to allow bootstrap caps to charge - // If brake_duty is NaN, this expression will also evaluate to true + // If brake_duty is NaN, this expression will also evaluate to false if ((brake_duty >= 0.0f) && (brake_duty <= 0.9f)) { int high_on = static_cast(TIM_APB1_PERIOD_CLOCKS * (1.0f - brake_duty)); int low_off = high_on - TIM_APB1_DEADTIME_CLOCKS; From 8455c0a5a6ebb5aeb083e43eaf3e91d2b53f40fe Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 23 Apr 2018 16:05:16 -0700 Subject: [PATCH 109/215] remove old unused version of shunt conductance --- Firmware/MotorControl/motor.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index fb85163d..950141bb 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -138,7 +138,6 @@ public: bool is_calibrated_ = config_.pre_calibrated; Iph_BC_t current_meas_ = {0.0f, 0.0f}; Iph_BC_t DC_calib_ = {0.0f, 0.0f}; - const float shunt_conductance_ = 1.0f / SHUNT_RESISTANCE; //[S] float phase_current_rev_gain_ = 0.0f; // Reverse gain for ADC to Amps (to be set by DRV8301_setup) Current_control_t current_control_ = { .p_gain = 0.0f, // [V/A] should be auto set after resistance and inductance measurement @@ -165,7 +164,6 @@ public: make_protocol_ro_property("current_meas_phC", ¤t_meas_.phC), make_protocol_property("DC_calib_phB", &DC_calib_.phB), make_protocol_property("DC_calib_phC", &DC_calib_.phC), - make_protocol_property("shunt_conductance", &shunt_conductance_), make_protocol_property("phase_current_rev_gain", &phase_current_rev_gain_), make_protocol_object("current_control", make_protocol_property("p_gain", ¤t_control_.p_gain), From 535dbcf48132bb5428d33dc62d6e3b2bb1fd2418 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 23 Apr 2018 15:12:25 -0700 Subject: [PATCH 110/215] add system_stats to monitor resource usage --- Firmware/Board/v3/Inc/FreeRTOSConfig.h | 2 +- Firmware/Board/v3/Inc/freertos_vars.h | 3 +++ Firmware/Board/v3/Src/freertos.c | 4 +++- Firmware/MotorControl/main.cpp | 24 +++++++++++++++++++++-- Firmware/MotorControl/odrive_main.h | 15 +++++++++++++- Firmware/communication/communication.cpp | 21 ++++++++++++++------ Firmware/communication/communication.h | 4 ++++ Firmware/communication/interface_uart.cpp | 4 +++- Firmware/communication/interface_uart.h | 4 ++++ Firmware/communication/interface_usb.cpp | 3 ++- Firmware/communication/interface_usb.h | 3 +++ 11 files changed, 74 insertions(+), 13 deletions(-) diff --git a/Firmware/Board/v3/Inc/FreeRTOSConfig.h b/Firmware/Board/v3/Inc/FreeRTOSConfig.h index fd592cbe..53280d99 100644 --- a/Firmware/Board/v3/Inc/FreeRTOSConfig.h +++ b/Firmware/Board/v3/Inc/FreeRTOSConfig.h @@ -96,7 +96,7 @@ #define configUSE_PREEMPTION 1 #define configSUPPORT_STATIC_ALLOCATION 0 #define configSUPPORT_DYNAMIC_ALLOCATION 1 -#define configUSE_IDLE_HOOK 0 +#define configUSE_IDLE_HOOK 1 #define configUSE_TICK_HOOK 0 #define configCPU_CLOCK_HZ ( SystemCoreClock ) #define configTICK_RATE_HZ ((TickType_t)1000) diff --git a/Firmware/Board/v3/Inc/freertos_vars.h b/Firmware/Board/v3/Inc/freertos_vars.h index 2eb52d7d..6982ee28 100644 --- a/Firmware/Board/v3/Inc/freertos_vars.h +++ b/Firmware/Board/v3/Inc/freertos_vars.h @@ -8,4 +8,7 @@ extern osSemaphoreId sem_uart_dma; extern osSemaphoreId sem_usb_rx; extern osSemaphoreId sem_usb_tx; +extern osThreadId defaultTaskHandle; +extern osThreadId usb_irq_thread; + #endif /* __FREERTOS_H */ \ No newline at end of file diff --git a/Firmware/Board/v3/Src/freertos.c b/Firmware/Board/v3/Src/freertos.c index 28ead46d..f1d46642 100644 --- a/Firmware/Board/v3/Src/freertos.c +++ b/Firmware/Board/v3/Src/freertos.c @@ -68,6 +68,8 @@ osSemaphoreId sem_uart_dma; osSemaphoreId sem_usb_rx; osSemaphoreId sem_usb_tx; +osThreadId usb_irq_thread; + // Place FreeRTOS heap in core coupled memory for better performance __attribute__((section(".ccmram"))) uint8_t ucHeap[configTOTAL_HEAP_SIZE]; @@ -112,7 +114,7 @@ void usb_deferred_interrupt_thread(void * ctx) { void init_deferred_interrupts(void) { // Start USB interrupt handler thread osThreadDef(task_usb_pump, usb_deferred_interrupt_thread, osPriorityAboveNormal, 0, 512); - osThreadCreate(osThread(task_usb_pump), NULL); + usb_irq_thread = osThreadCreate(osThread(task_usb_pump), NULL); } /* USER CODE END 4 */ diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 56b57bb2..b250afe6 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -3,6 +3,10 @@ #include "odrive_main.h" #include "nvm_config.hpp" +#include "freertos_vars.h" +#include +#include + BoardConfig_t board_config; EncoderConfig_t encoder_configs[AXIS_COUNT]; ControllerConfig_t controller_configs[AXIS_COUNT]; @@ -10,7 +14,7 @@ MotorConfig_t motor_configs[AXIS_COUNT]; AxisConfig_t axis_configs[AXIS_COUNT]; bool user_config_loaded_; -bool user_config_loaded = false; +SystemStats_t system_stats_ = { 0 }; Axis *axes[AXIS_COUNT]; @@ -66,7 +70,22 @@ void enter_dfu_mode(void) { extern "C" { int odrive_main(void); -void vApplicationStackOverflowHook(void) { for(;;); } +void vApplicationStackOverflowHook(void) { + for (;;); // TODO: safe action +} +void vApplicationIdleHook(void) { + if (system_stats_.fully_booted) { + system_stats_.uptime = xTaskGetTickCount(); + system_stats_.min_heap_space = xPortGetMinimumEverFreeHeapSize(); + system_stats_.min_stack_space_comms = uxTaskGetStackHighWaterMark(comm_thread); + system_stats_.min_stack_space_axis0 = uxTaskGetStackHighWaterMark(axes[0]->thread_id_); + system_stats_.min_stack_space_axis1 = uxTaskGetStackHighWaterMark(axes[1]->thread_id_); + system_stats_.min_stack_space_usb = uxTaskGetStackHighWaterMark(usb_thread); + system_stats_.min_stack_space_uart = uxTaskGetStackHighWaterMark(uart_thread); + system_stats_.min_stack_space_usb_irq = uxTaskGetStackHighWaterMark(usb_irq_thread); + system_stats_.min_stack_space_startup = uxTaskGetStackHighWaterMark(defaultTaskHandle); + } +} } int odrive_main(void) { @@ -121,5 +140,6 @@ int odrive_main(void) { axes[i]->start_thread(); } + system_stats_.fully_booted = true; return 0; } diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 0b75af48..bddf7e88 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -29,11 +29,24 @@ extern float vbus_voltage; extern bool brake_resistor_armed_; extern const float elec_rad_per_enc; extern uint32_t _reboot_cookie; -extern bool user_config_loaded; +extern bool user_config_loaded_; extern uint64_t serial_number; extern char serial_number_str[13]; +typedef struct { + bool fully_booted; + uint32_t uptime; // [ms] + uint32_t min_heap_space; // FreeRTOS heap [Bytes] + uint32_t min_stack_space_axis0; // minimum remaining space since startup [Bytes] + uint32_t min_stack_space_axis1; + uint32_t min_stack_space_comms; + uint32_t min_stack_space_usb; + uint32_t min_stack_space_uart; + uint32_t min_stack_space_usb_irq; + uint32_t min_stack_space_startup; +} SystemStats_t; +extern SystemStats_t system_stats_; #ifdef __cplusplus } diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index ed2c1d0d..93f25508 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -62,6 +62,8 @@ const uint8_t fw_version_minor = FW_VERSION_MINOR; const uint8_t fw_version_revision = FW_VERSION_REVISION; const uint8_t fw_version_unreleased = FW_VERSION_UNRELEASED; // 0 for official releases, 1 otherwise +osThreadId comm_thread; + /* Private function prototypes -----------------------------------------------*/ /* Function implementations --------------------------------------------------*/ @@ -70,7 +72,7 @@ void init_communication(void) { // Start command handling thread osThreadDef(task_cmd_parse, communication_task, osPriorityNormal, 0, 5000 /* in 32-bit words */); // TODO: fix stack issues - osThreadCreate(osThread(task_cmd_parse), NULL); + comm_thread = osThreadCreate(osThread(task_cmd_parse), NULL); } @@ -80,8 +82,6 @@ float oscilloscope[OSCILLOSCOPE_SIZE] = { size_t oscilloscope_pos = 0; -uint32_t comm_stack_info = 0; // for debugging only - // Helper class because the protocol library doesn't yet // support non-member functions // TODO: make this go away @@ -101,7 +101,6 @@ public: static inline auto make_obj_tree() { return make_protocol_member_list( make_protocol_ro_property("vbus_voltage", &vbus_voltage), - make_protocol_ro_property("comm_stack_info", &comm_stack_info), make_protocol_ro_property("serial_number", &serial_number), make_protocol_ro_property("hw_version_major", &hw_version_major), make_protocol_ro_property("hw_version_minor", &hw_version_minor), @@ -110,8 +109,19 @@ static inline auto make_obj_tree() { make_protocol_ro_property("fw_version_minor", &fw_version_minor), make_protocol_ro_property("fw_version_revision", &fw_version_revision), make_protocol_ro_property("fw_version_unreleased", &fw_version_unreleased), - make_protocol_ro_property("user_config_loaded", const_cast(&user_config_loaded)), + make_protocol_ro_property("user_config_loaded", const_cast(&user_config_loaded_)), make_protocol_ro_property("brake_resistor_armed", &brake_resistor_armed_), + make_protocol_object("system_stats", + make_protocol_ro_property("uptime", &system_stats_.uptime), + make_protocol_ro_property("min_heap_space", &system_stats_.min_heap_space), + make_protocol_ro_property("min_stack_space_axis0", &system_stats_.min_stack_space_axis0), + make_protocol_ro_property("min_stack_space_axis1", &system_stats_.min_stack_space_axis1), + make_protocol_ro_property("min_stack_space_comms", &system_stats_.min_stack_space_comms), + make_protocol_ro_property("min_stack_space_usb", &system_stats_.min_stack_space_usb), + make_protocol_ro_property("min_stack_space_uart", &system_stats_.min_stack_space_uart), + make_protocol_ro_property("min_stack_space_usb_irq", &system_stats_.min_stack_space_usb_irq), + make_protocol_ro_property("min_stack_space_startup", &system_stats_.min_stack_space_startup) + ), make_protocol_object("config", make_protocol_property("brake_resistance", &board_config.brake_resistance), // TODO: changing this currently requires a reboot - fix this @@ -150,7 +160,6 @@ void communication_task(void * ctx) { auto tree_ptr = new (tree_buffer) tree_type(make_obj_tree()); auto endpoint_provider = EndpointProvider_from_MemberList(*tree_ptr); set_application_endpoints(&endpoint_provider); - comm_stack_info = uxTaskGetStackHighWaterMark(nullptr); serve_on_uart(); serve_on_usb(); diff --git a/Firmware/communication/communication.h b/Firmware/communication/communication.h index 9d1dff2e..8e68e508 100644 --- a/Firmware/communication/communication.h +++ b/Firmware/communication/communication.h @@ -13,6 +13,10 @@ extern "C" { #endif +#include + +extern osThreadId comm_thread; + void init_communication(void); void communication_task(void * ctx); diff --git a/Firmware/communication/interface_uart.cpp b/Firmware/communication/interface_uart.cpp index d83442af..90d976aa 100644 --- a/Firmware/communication/interface_uart.cpp +++ b/Firmware/communication/interface_uart.cpp @@ -21,6 +21,8 @@ static uint32_t dma_last_rcv_idx; // FIXME: the stdlib doesn't know about CMSIS threads, so this is just a global variable static thread_local uint32_t deadline_ms = 0; +osThreadId uart_thread; + class UART4Sender : public StreamSink { public: @@ -93,7 +95,7 @@ void serve_on_uart() { // Start UART communication thread osThreadDef(uart_server_thread_def, uart_server_thread, osPriorityNormal, 0, 512); - osThreadCreate(osThread(uart_server_thread_def), NULL); + uart_thread = osThreadCreate(osThread(uart_server_thread_def), NULL); } void HAL_UART_TxCpltCallback(UART_HandleTypeDef* huart) { diff --git a/Firmware/communication/interface_uart.h b/Firmware/communication/interface_uart.h index 02c47331..b5f1ed72 100644 --- a/Firmware/communication/interface_uart.h +++ b/Firmware/communication/interface_uart.h @@ -5,6 +5,10 @@ extern "C" { #endif +#include + +extern osThreadId uart_thread; + void serve_on_uart(void); #ifdef __cplusplus diff --git a/Firmware/communication/interface_usb.cpp b/Firmware/communication/interface_usb.cpp index 0bca55c1..541128b2 100644 --- a/Firmware/communication/interface_usb.cpp +++ b/Firmware/communication/interface_usb.cpp @@ -16,6 +16,7 @@ static uint32_t usb_len; // FIXME: the stdlib doesn't know about CMSIS threads, so this is just a global variable static thread_local uint32_t deadline_ms = 0; +osThreadId usb_thread; class USBSender : public PacketSink { @@ -99,5 +100,5 @@ void usb_process_packet(uint8_t *buf, uint32_t len) { void serve_on_usb() { // Start USB communication thread osThreadDef(usb_server_thread_def, usb_server_thread, osPriorityNormal, 0, 512); - osThreadCreate(osThread(usb_server_thread_def), NULL); + usb_thread = osThreadCreate(osThread(usb_server_thread_def), NULL); } diff --git a/Firmware/communication/interface_usb.h b/Firmware/communication/interface_usb.h index 3602843f..27d40a10 100644 --- a/Firmware/communication/interface_usb.h +++ b/Firmware/communication/interface_usb.h @@ -5,8 +5,11 @@ extern "C" { #endif +#include #include +extern osThreadId usb_thread; + void usb_process_packet(uint8_t *buf, uint32_t len); void serve_on_usb(void); From dc303606b14c0180ebfa6c270db6b0ec4b47de6e Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 23 Apr 2018 19:03:42 -0700 Subject: [PATCH 111/215] fix USB TX lockup issue When the host reset the connection without reading the response packet first, the TX-empty semaphore would never get released, thereby blocking any further TX communication. This commit just overrides the TX buffer if the semaphore wait times out. --- Firmware/MotorControl/main.cpp | 14 +++++++------- Firmware/communication/communication.cpp | 7 ++++++- Firmware/communication/interface_usb.cpp | 18 +++++++++++++++--- Firmware/communication/interface_usb.h | 8 ++++++++ tools/odrive/protocol.py | 1 + 5 files changed, 37 insertions(+), 11 deletions(-) diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index b250afe6..9970e84b 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -77,13 +77,13 @@ void vApplicationIdleHook(void) { if (system_stats_.fully_booted) { system_stats_.uptime = xTaskGetTickCount(); system_stats_.min_heap_space = xPortGetMinimumEverFreeHeapSize(); - system_stats_.min_stack_space_comms = uxTaskGetStackHighWaterMark(comm_thread); - system_stats_.min_stack_space_axis0 = uxTaskGetStackHighWaterMark(axes[0]->thread_id_); - system_stats_.min_stack_space_axis1 = uxTaskGetStackHighWaterMark(axes[1]->thread_id_); - system_stats_.min_stack_space_usb = uxTaskGetStackHighWaterMark(usb_thread); - system_stats_.min_stack_space_uart = uxTaskGetStackHighWaterMark(uart_thread); - system_stats_.min_stack_space_usb_irq = uxTaskGetStackHighWaterMark(usb_irq_thread); - system_stats_.min_stack_space_startup = uxTaskGetStackHighWaterMark(defaultTaskHandle); + system_stats_.min_stack_space_comms = uxTaskGetStackHighWaterMark(comm_thread) * sizeof(StackType_t); + system_stats_.min_stack_space_axis0 = uxTaskGetStackHighWaterMark(axes[0]->thread_id_) * sizeof(StackType_t); + system_stats_.min_stack_space_axis1 = uxTaskGetStackHighWaterMark(axes[1]->thread_id_) * sizeof(StackType_t); + system_stats_.min_stack_space_usb = uxTaskGetStackHighWaterMark(usb_thread) * sizeof(StackType_t); + system_stats_.min_stack_space_uart = uxTaskGetStackHighWaterMark(uart_thread) * sizeof(StackType_t); + system_stats_.min_stack_space_usb_irq = uxTaskGetStackHighWaterMark(usb_irq_thread) * sizeof(StackType_t); + system_stats_.min_stack_space_startup = uxTaskGetStackHighWaterMark(defaultTaskHandle) * sizeof(StackType_t); } } } diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 93f25508..86891a8f 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -120,7 +120,12 @@ static inline auto make_obj_tree() { make_protocol_ro_property("min_stack_space_usb", &system_stats_.min_stack_space_usb), make_protocol_ro_property("min_stack_space_uart", &system_stats_.min_stack_space_uart), make_protocol_ro_property("min_stack_space_usb_irq", &system_stats_.min_stack_space_usb_irq), - make_protocol_ro_property("min_stack_space_startup", &system_stats_.min_stack_space_startup) + make_protocol_ro_property("min_stack_space_startup", &system_stats_.min_stack_space_startup), + make_protocol_object("usb", + make_protocol_ro_property("rx_cnt", &usb_stats_.rx_cnt), + make_protocol_ro_property("tx_cnt", &usb_stats_.tx_cnt), + make_protocol_ro_property("tx_overrun_cnt", &usb_stats_.tx_overrun_cnt) + ) ), make_protocol_object("config", make_protocol_property("brake_resistance", &board_config.brake_resistance), diff --git a/Firmware/communication/interface_usb.cpp b/Firmware/communication/interface_usb.cpp index 541128b2..cee94a99 100644 --- a/Firmware/communication/interface_usb.cpp +++ b/Firmware/communication/interface_usb.cpp @@ -18,6 +18,7 @@ static thread_local uint32_t deadline_ms = 0; osThreadId usb_thread; +USBStats_t usb_stats_ = {0}; class USBSender : public PacketSink { public: @@ -26,13 +27,23 @@ public: if (length > USB_TX_DATA_SIZE) return -1; // wait for USB interface to become ready - if (osSemaphoreWait(sem_usb_tx, deadline_to_timeout(deadline_ms)) != osOK) - return -1; + if (osSemaphoreWait(sem_usb_tx, deadline_to_timeout(deadline_ms)) != osOK) { + // If the host resets the device it might be that the TX-complete handler is never called + // and the sem_usb_tx semaphore is never released. To handle this we just override the + // TX buffer if this wait times out. The implication is that the channel is no longer lossless. + // TODO: handle endpoint reset properly + usb_stats_.tx_overrun_cnt++; + } // transmit packet uint8_t status = CDC_Transmit_FS( const_cast(buffer) /* casting this const away is safe because... well... it's not actually. Stupid STM. */, length); - return (status == USBD_OK) ? 0 : -1; + if (status != USBD_OK) { + osSemaphoreRelease(sem_usb_tx); + return -1; + } + usb_stats_.tx_cnt = 0; + return 0; } } usb_packet_output; @@ -76,6 +87,7 @@ static void usb_server_thread(void * ctx) { const uint32_t usb_check_timeout = 1; // ms osStatus sem_stat = osSemaphoreWait(sem_usb_rx, usb_check_timeout); if (sem_stat == osOK) { + usb_stats_.rx_cnt++; deadline_ms = timeout_to_deadline(PROTOCOL_SERVER_TIMEOUT_MS); #if defined(USB_PROTOCOL_NATIVE) usb_channel.process_packet(usb_buf, usb_len); diff --git a/Firmware/communication/interface_usb.h b/Firmware/communication/interface_usb.h index 27d40a10..a56bca36 100644 --- a/Firmware/communication/interface_usb.h +++ b/Firmware/communication/interface_usb.h @@ -10,6 +10,14 @@ extern "C" { extern osThreadId usb_thread; +typedef struct { + uint32_t rx_cnt; + uint32_t tx_cnt; + uint32_t tx_overrun_cnt; +} USBStats_t; + +extern USBStats_t usb_stats_; + void usb_process_packet(uint8_t *buf, uint32_t len); void serve_on_usb(void); diff --git a/tools/odrive/protocol.py b/tools/odrive/protocol.py index 4c11971c..09d26707 100644 --- a/tools/odrive/protocol.py +++ b/tools/odrive/protocol.py @@ -345,6 +345,7 @@ class Channel(PacketSink): if (ack_signal): self._responses[seq_no] = packet[2:] ack_signal.set() + #print("received ack for packet " + str(seq_no)) else: print("received unexpected ACK: " + str(seq_no)) From 9d04108b1073a5c548e1c72952808fb66b94daf4 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 23 Apr 2018 19:07:21 -0700 Subject: [PATCH 112/215] make GPIO interrupts work for all pin numbers --- Firmware/Board/v3/Src/stm32f4xx_it.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Firmware/Board/v3/Src/stm32f4xx_it.c b/Firmware/Board/v3/Src/stm32f4xx_it.c index a7fe3a12..1a650abc 100644 --- a/Firmware/Board/v3/Src/stm32f4xx_it.c +++ b/Firmware/Board/v3/Src/stm32f4xx_it.c @@ -346,6 +346,11 @@ void EXTI4_IRQHandler(void) */ void EXTI9_5_IRQHandler(void) { + // The true source of the interrupt is checked inside HAL_GPIO_EXTI_IRQHandler() + HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_5); + HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_6); + HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_7); + HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_8); HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_9); } @@ -354,6 +359,12 @@ void EXTI9_5_IRQHandler(void) */ void EXTI15_10_IRQHandler(void) { + // The true source of the interrupt is checked inside HAL_GPIO_EXTI_IRQHandler() + HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_10); + HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_11); + HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12); + HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13); + HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_14); HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15); } From cc1eddc65edc69e45bb8b4b48c97e3e0f14335bd Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 23 Apr 2018 19:52:39 -0700 Subject: [PATCH 113/215] store the programmer serial number with escape characters. An STLink/v2 programmer is now identified by a string of the format "\x12\x34..." instead of "1234..." This navigates around differences of how makefiles are parsed in windows and linux --- Firmware/Makefile | 3 +-- Firmware/find_programmer.sh | 5 ++++- tools/test-rig-loopback.yaml | 4 ++-- tools/test-rig-parallel.yaml | 4 ++-- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Firmware/Makefile b/Firmware/Makefile index eee7c551..4b823b60 100644 --- a/Firmware/Makefile +++ b/Firmware/Makefile @@ -5,9 +5,8 @@ BUILD_DIR = build FIRMWARE = $(BUILD_DIR)/ODriveFirmware.elf FIRMWARE_HEX = $(BUILD_DIR)/ODriveFirmware.hex -PROGRAMMER_HEX := $(shell echo $(PROGRAMMER) | sed -e 's/.\\{2\\}/\\\\x&/g') OPENOCD := openocd -f interface/stlink-v2.cfg \ - $(if $(value PROGRAMMER),-c 'hla_serial $(PROGRAMMER_HEX)',) \ + $(if $(value PROGRAMMER),-c 'hla_serial $(PROGRAMMER)',) \ -f target/stm32f4x.cfg diff --git a/Firmware/find_programmer.sh b/Firmware/find_programmer.sh index 97a94928..4b184b6a 100755 --- a/Firmware/find_programmer.sh +++ b/Firmware/find_programmer.sh @@ -1,2 +1,5 @@ #!/bin/bash -openocd -d3 -f board/stm32f4discovery.cfg -c "hla_serial wrong_serial" 2>&1 | xxd -p | tr -d '\n' | sed -n 's/^.*6e756d6265722027\([0-9a-f]*\)2720646f65736e27.*$/\1/p'; echo +openocd -d3 -f board/stm32f4discovery.cfg -c "hla_serial wrong_serial" 2>&1 | \ + xxd -p | \ + tr -d '\n' | \ + sed -n 's/^.*6e756d6265722027\([0-9a-f]*\)2720646f65736e27.*$/\1/p' | sed -e 's/.\{2\}/\\x&/g'; echo diff --git a/tools/test-rig-loopback.yaml b/tools/test-rig-loopback.yaml index 359c3a61..12b87235 100644 --- a/tools/test-rig-loopback.yaml +++ b/tools/test-rig-loopback.yaml @@ -8,7 +8,7 @@ odrives: brake-resistance: 0.47 uart: /dev/serial/by-id/[not-yet-used] usb: auto - programmer: '493f6f06493f56540929113f' + programmer: '\x49\x3f\x6f\x06\x49\x3f\x56\x54\x09\x29\x11\x3f' vbus-voltage: 24 # [V] max-brake-power: 150 # [W] axes: @@ -38,7 +38,7 @@ odrives: brake-resistance: 0.47 uart: /dev/serial/by-id/[not-yet-used] usb: auto - programmer: '533f7506493f49514454193f' + programmer: '\x53\x3f\x75\x06\x49\x3f\x49\x51\x44\x54\x19\x3f' vbus-voltage: 48 # [V] max-brake-power: 150 # [W] axes: diff --git a/tools/test-rig-parallel.yaml b/tools/test-rig-parallel.yaml index e7559105..47173166 100644 --- a/tools/test-rig-parallel.yaml +++ b/tools/test-rig-parallel.yaml @@ -9,7 +9,7 @@ odrives: brake-resistance: 0.47 uart: /dev/serial/by-id/[not-yet-used] usb: auto - programmer: '533f7506493f49514454193f' + programmer: '\x53\x3f\x75\x06\x49\x3f\x49\x51\x44\x54\x19\x3f' vbus-voltage: 24 # [V] max-brake-power: 150 # [W] axes: @@ -39,7 +39,7 @@ odrives: brake-resistance: 0.47 uart: /dev/serial/by-id/[not-yet-used] usb: auto - programmer: '493f6f06493f56540929113f' + programmer: '\x49\x3f\x6f\x06\x49\x3f\x56\x54\x09\x29\x11\x3f' vbus-voltage: 24 # [V] max-brake-power: 150 # [W] axes: From 8b696d29e0de27656626f09e7ab2ee13c54f566d Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 23 Apr 2018 20:10:02 -0700 Subject: [PATCH 114/215] change pos_cpr to pos_cpr_ --- Firmware/MotorControl/encoder.cpp | 10 +++++----- Firmware/MotorControl/encoder.hpp | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 0e736ae1..6267e152 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -71,7 +71,7 @@ void Encoder::set_circular_count(int32_t count) { offset_ = mod(offset_, config_.cpr); // Update states count_in_cpr_ = mod(count, config_.cpr); - pos_cpr = (float)count_in_cpr_; + pos_cpr_ = (float)count_in_cpr_; __set_PRIMASK(prim); } @@ -233,15 +233,15 @@ bool Encoder::update(float* pos_estimate, float* vel_estimate, float* phase_outp // run pll (for now pll is in units of encoder counts) // Predict current pos pos_estimate_ += current_meas_period * pll_vel_; - pos_cpr += current_meas_period * pll_vel_; + pos_cpr_ += current_meas_period * pll_vel_; // discrete phase detector float delta_pos = (float)(shadow_count_ - (int32_t)floorf(pos_estimate_)); - float delta_pos_cpr = (float)(count_in_cpr_ - (int32_t)floorf(pos_cpr)); + float delta_pos_cpr = (float)(count_in_cpr_ - (int32_t)floorf(pos_cpr_)); delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * (float)(config_.cpr)); // pll feedback pos_estimate_ += current_meas_period * pll_kp_ * delta_pos; - pos_cpr += current_meas_period * pll_kp_ * delta_pos_cpr; - pos_cpr = fmodf_pos(pos_cpr, (float)(config_.cpr)); + pos_cpr_ += current_meas_period * pll_kp_ * delta_pos_cpr; + pos_cpr_ = fmodf_pos(pos_cpr_, (float)(config_.cpr)); pll_vel_ += current_meas_period * pll_ki_ * delta_pos_cpr; if (fabsf(pll_vel_) < 0.5f * current_meas_period * pll_ki_) pll_vel_ = 0.0f; //align delta-sigma on zero to prevent jitter diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index a97b94a1..9a59654e 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -56,7 +56,7 @@ public: int32_t offset_ = 0; float phase_ = 0.0f; // [rad] float pos_estimate_ = 0.0f; // [rad] - float pos_cpr = 0.0f; // [rad] + float pos_cpr_ = 0.0f; // [rad] float pll_vel_ = 0.0f; // [rad/s] float pll_kp_ = 0.0f; // [rad/s / rad] float pll_ki_ = 0.0f; // [(rad/s^2) / rad] @@ -72,7 +72,7 @@ public: make_protocol_property("offset", &offset_), make_protocol_property("phase", &phase_), make_protocol_property("pos_estimate", &pos_estimate_), - make_protocol_property("pos_cpr", &pos_cpr), + make_protocol_property("pos_cpr", &pos_cpr_), make_protocol_property("pll_vel", &pll_vel_), make_protocol_property("pll_kp", &pll_kp_), make_protocol_property("pll_ki", &pll_ki_), From f3a484d6dbed32c5b2cb4317eeb5d5dee36d93b6 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 23 Apr 2018 20:21:18 -0700 Subject: [PATCH 115/215] fix deadtime violation check polarity --- Firmware/MotorControl/low_level.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index f84b90fc..a984b789 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -182,7 +182,7 @@ void safety_critical_disarm_brake_resistor() { // @brief Updates the brake resistor PWM timings unless // the brake resistor is disarmed. void safety_critical_apply_brake_resistor_timings(uint32_t low_off, uint32_t high_on) { - if (high_on - low_off > TIM_APB1_DEADTIME_CLOCKS) + if (high_on - low_off < TIM_APB1_DEADTIME_CLOCKS) for(;;); uint8_t sr = cpu_enter_critical(); if (brake_resistor_armed_) { From 3125044b96dc42b551d84cc11d5a0b52cdadb61c Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 23 Apr 2018 20:26:41 -0700 Subject: [PATCH 116/215] amend changelog --- Firmware/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Firmware/CHANGELOG.md b/Firmware/CHANGELOG.md index eb7c0769..66a71605 100644 --- a/Firmware/CHANGELOG.md +++ b/Firmware/CHANGELOG.md @@ -6,6 +6,9 @@ Please add a note of your changes below this heading if you make a Pull Request. * `make write_otp` command to burn the board version onto the ODrive's one-time programmable memory. If you have an ODrive v3.4 or older, you should run this once for a better firmware update user experience in the future. Run the command without any options for more details. Once set, the board version is exposed through the `hw_version_[...]` properties. * bake Git-derived firmware version into firmware binary. The firmware version is exposed through the `fw_version_[...]` properties. * infrastructure to publish the python tools to PyPi. See `tools/setup.py` for details. + * Automated test script `run_tests.py` + * Protocol supports function return values + * System stats (e.g. stack usage) are exposed under `.system_stats` ### Changed * The DFU script now verifies the flash after writing @@ -24,6 +27,7 @@ Please add a note of your changes below this heading if you make a Pull Request. ### Fixed * Enums now transported with correct underlying type on native protocol +* USB issue where the device would stop responding when the host script would quit abruptly or reset the device during operation # Releases From 9854278bf5eff5f5c0a0b325e5112c6946897211 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 23 Apr 2018 22:39:46 -0700 Subject: [PATCH 117/215] [HOTFIX] add delay before entering DFU mode, may short the brake resistor FETs otherwise --- Firmware/Board/v3/Src/main.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Firmware/Board/v3/Src/main.c b/Firmware/Board/v3/Src/main.c index 644799de..20f4ba8a 100644 --- a/Firmware/Board/v3/Src/main.c +++ b/Firmware/Board/v3/Src/main.c @@ -100,6 +100,21 @@ int main(void) { /* USER CODE BEGIN 1 */ + /* + * This wait loop works around an obscure timing issue. + * When the transition NVIC_SystemReset() => STM bootloader happens quickly, + * there is a yet unexplained phenomenon where both the high side and low side + * brake resistor FETs would turn on simultaneously for about 2.5ms. + * This manifests in an audible click and may lead to failure of the FETs. + * When adding a delay before entering DFU mode the issue does not occur. + * + * This loop takes about 5 cycles per iteration, so the delay + * is about 1/168000kHz*5*1000000 = 30ms + */ + for (size_t i = 0; i < 1000000; ++i) { + __NOP(); + } + /* We could jump to the bootloader directly on demand without rebooting but that requires us to reset several peripherals and interrupts for it to function correctly. Therefore it's easier to just reset the entire chip. */ From 64505c0428d21881861b6c7229e2b7970aba2c65 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 23 Apr 2018 22:39:46 -0700 Subject: [PATCH 118/215] [HOTFIX] add delay before entering DFU mode, may short the brake resistor FETs otherwise --- Firmware/Board/v3/Src/main.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Firmware/Board/v3/Src/main.c b/Firmware/Board/v3/Src/main.c index 644799de..20f4ba8a 100644 --- a/Firmware/Board/v3/Src/main.c +++ b/Firmware/Board/v3/Src/main.c @@ -100,6 +100,21 @@ int main(void) { /* USER CODE BEGIN 1 */ + /* + * This wait loop works around an obscure timing issue. + * When the transition NVIC_SystemReset() => STM bootloader happens quickly, + * there is a yet unexplained phenomenon where both the high side and low side + * brake resistor FETs would turn on simultaneously for about 2.5ms. + * This manifests in an audible click and may lead to failure of the FETs. + * When adding a delay before entering DFU mode the issue does not occur. + * + * This loop takes about 5 cycles per iteration, so the delay + * is about 1/168000kHz*5*1000000 = 30ms + */ + for (size_t i = 0; i < 1000000; ++i) { + __NOP(); + } + /* We could jump to the bootloader directly on demand without rebooting but that requires us to reset several peripherals and interrupts for it to function correctly. Therefore it's easier to just reset the entire chip. */ From 5735d5cd96afa570051737f140991dad08937f02 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 24 Apr 2018 12:20:08 -0700 Subject: [PATCH 119/215] [firmware] disable DFU feature for board version <= 3.4 because it can break the board --- Firmware/Board/v3/Src/main.c | 33 ++++++++++++++++++------------ Firmware/MotorControl/commands.cpp | 21 ++++++++++++++----- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/Firmware/Board/v3/Src/main.c b/Firmware/Board/v3/Src/main.c index 20f4ba8a..1691e4a6 100644 --- a/Firmware/Board/v3/Src/main.c +++ b/Firmware/Board/v3/Src/main.c @@ -100,19 +100,26 @@ int main(void) { /* USER CODE BEGIN 1 */ - /* - * This wait loop works around an obscure timing issue. - * When the transition NVIC_SystemReset() => STM bootloader happens quickly, - * there is a yet unexplained phenomenon where both the high side and low side - * brake resistor FETs would turn on simultaneously for about 2.5ms. - * This manifests in an audible click and may lead to failure of the FETs. - * When adding a delay before entering DFU mode the issue does not occur. - * - * This loop takes about 5 cycles per iteration, so the delay - * is about 1/168000kHz*5*1000000 = 30ms - */ - for (size_t i = 0; i < 1000000; ++i) { - __NOP(); + if(*((unsigned long *)0x2001C000) == 0xDEADFE75) { + /* The STM DFU bootloader enables internal pull-up resistors on PB10 (AUX_H) + * and PB11 (AUX_L), thereby causing shoot-through on the brake resistor + * FETs and obliterating them unless external 3.3k pull-down resistors are + * present. Pull-downs are only present on ODrive 3.5 or newer. + * On older boards we disable DFU by default but if the user insists + * there's only one thing left that might save it: time. + * The brake resistor gate driver needs a certain 10V supply (GVDD) to + * make it work. This voltage is supplied by the motor gate drivers which get + * disabled at system reset. So over time GVDD voltage _should_ below + * dangerous levels. This is completely handwavy and should not be relied on + * so you are on your own on if you ignore this warning. + * + * This loop takes 5 cycles per iteration and at this point the system runs + * on the internal 16MHz RC oscillator so the delay is about 2 seconds. + */ + for (size_t i = 0; i < (16000000UL / 5UL * 2UL); ++i) { + __NOP(); + } + *((unsigned long *)0x2001C000) == 0xDEADBEEF; } /* We could jump to the bootloader directly on demand without rebooting diff --git a/Firmware/MotorControl/commands.cpp b/Firmware/MotorControl/commands.cpp index 4f8453d8..2ca2a066 100644 --- a/Firmware/MotorControl/commands.cpp +++ b/Firmware/MotorControl/commands.cpp @@ -109,11 +109,6 @@ void motors_run_anticogging_calibration_func() { } } -void enter_dfu_mode() { - *((unsigned long *)0x2001C000) = 0xDEADBEEF; - NVIC_SystemReset(); -} - #if HW_VERSION_MAJOR == 3 // Determine start address of the OTP struct: // The OTP is organized into 16-byte blocks. @@ -142,6 +137,22 @@ const uint8_t fw_version_minor = FW_VERSION_MINOR; const uint8_t fw_version_revision = FW_VERSION_REVISION; const uint8_t fw_version_unreleased = FW_VERSION_UNRELEASED; // 0 for official releases, 1 otherwise +void enter_dfu_mode() { + if ((board_version_major == 3) && (board_version_minor >= 5)) { + *((unsigned long *)0x2001C000) = 0xDEADBEEF; + NVIC_SystemReset(); + } else { + /* + * DFU mode is only allowed on board version >= 3.5 because it can burn + * the brake resistor FETs on older boards. + * If you really want to use it on an older board, add 3.3k pull-down resistors + * to the AUX_L and AUX_H signals and _only then_ uncomment these lines. + */ + //*((unsigned long *)0x2001C000) = 0xDEADFE75; + //NVIC_SystemReset(); + } +} + // This table specifies which fields and functions are exposed on the USB and UART ports. // TODO: Autogenerate this table. It will come up again very soon in the Arduino library. // clang-format off From 86a21838eac2e7742a521a2d782f60c539f08936 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 24 Apr 2018 12:32:33 -0700 Subject: [PATCH 120/215] [odrivetool] disable DFU feature for board version <= 3.4 because it can break the board --- tools/odrive/dfu.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/tools/odrive/dfu.py b/tools/odrive/dfu.py index d1b59f82..1efd4592 100755 --- a/tools/odrive/dfu.py +++ b/tools/odrive/dfu.py @@ -206,7 +206,7 @@ def show_deferred_message(message, cancellation_token): t.daemon = True t.start() -def put_odrive_into_dfu_mode(my_drive): +def put_odrive_into_dfu_mode(my_drive, cancellation_token): """ Puts the specified device into DFU mode """ @@ -216,15 +216,23 @@ def put_odrive_into_dfu_mode(my_drive): "DFU with this script should work fine." .format(my_drive.__channel__.usb_device.serial_number)) return - print("Putting device {} into DFU mode...".format(my_drive.__channel__.usb_device.serial_number)) - try: - my_drive.enter_dfu_mode() - except odrive.protocol.ChannelBrokenException as ex: - pass # this is expected because the device reboots - if platform.system() == "Windows": - show_deferred_message("Still waiting for the device to reappear.\n" - "Use the Zadig utility to set the driver of 'STM32 BOOTLOADER' to libusb-win32.", - find_odrive_cancellation_token) + hw_version_major = my_drive.hw_version_major if hasattr(my_drive, 'hw_version_major') else 3 + hw_version_minor = my_drive.hw_version_minor if hasattr(my_drive, 'hw_version_minor') else 4 + if hw_version_major == 3 and hw_version_minor >= 5: + print("Putting device {} into DFU mode...".format(my_drive.__channel__.usb_device.serial_number)) + try: + my_drive.enter_dfu_mode() + except odrive.protocol.ChannelBrokenException: + pass # this is expected because the device reboots + if platform.system() == "Windows": + show_deferred_message("Still waiting for the device to reappear.\n" + "Use the Zadig utility to set the driver of 'STM32 BOOTLOADER' to libusb-win32.", + cancellation_token) + else: + print("Found device {}".format(my_drive.__channel__.usb_device.serial_number)) + print(" DFU mode is not supported on board version 3.4 or earlier.") + print(" This is because entering DFU mode on such a device would") + print(" break the brake resistor FETs under some circumstances.") def launch_dfu(args, app_shutdown_token): """ @@ -251,7 +259,9 @@ def launch_dfu(args, app_shutdown_token): # Scan for ODrives not in DFU mode and put them into DFU mode once they appear # We only scan on USB because DFU is only possible over USB - odrive.discovery.find_all(args.path, serial_number, put_odrive_into_dfu_mode, find_odrive_cancellation_token, app_shutdown_token) + odrive.discovery.find_all(args.path, serial_number, + lambda dev: put_odrive_into_dfu_mode(dev, find_odrive_cancellation_token), + find_odrive_cancellation_token, app_shutdown_token) # Poll libUSB until a device in DFU mode is found while not app_shutdown_token.is_set(): From 32e76bf2cef5e0d2e3b5f43e8c36a1d9b69b3470 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Tue, 24 Apr 2018 12:54:03 -0700 Subject: [PATCH 121/215] DFU not available for v3.4 or earlier --- Firmware/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firmware/README.md b/Firmware/README.md index e4df6b1f..d4bd4ab9 100644 --- a/Firmware/README.md +++ b/Firmware/README.md @@ -49,7 +49,7 @@ __CONFIG_STEP_DIR__: Set to `y` to use the GPIO1 and GPIO2 for step/direction in

## Downloading and Installing Tools ### Getting a programmer -__Note:__ If you don't plan to make major firmware modifications you can use the built-in DFU feature. +__Note:__ If you have ODrive v3.5 and newer, and don't plan to make major firmware modifications you can use the built-in DFU feature. In this case you don't need an SWD programmer and you can skip OpenOCD related instructions. Get a programmer that supports SWD (Serial Wire Debugging) and is ST-link v2 compatible. You can get them really cheap on [eBay](http://www.ebay.co.uk/itm/ST-Link-V2-Emulator-Downloader-Programming-Mini-Unit-STM8-STM32-with-20CM-Line-/391173940927?hash=item5b13c8a6bf:g:3g8AAOSw~OdVf-Tu) or many other places. @@ -96,7 +96,7 @@ After installing all of the above, open a Git Bash shell. Continue at section [B * Run `make` in the `Firmware` directory. ### Flashing the firmware (standalone device) -Note: ODrive v3.4 and earlier require you to flash with the external programmer first (see below), before you can reflash in standalone mode. +Note: This method of updating the firmware is only supported on ODrive v3.5 and newer. If you have an older board you must instead use the method in the [next section](#flashing-the-firmware). * __Windows__: Use the [Zadig](http://zadig.akeo.ie/) utility to set ODrive (not STLink!) driver to libusb-win32. * If 'Odrive version 3.x' is not in the list of devices upon opening Zadig, check 'List All Devices' from the options menu. With the Odrive selected in the device list choose 'libusb-win32' from the target driver list and select the large 'install driver' button. * Run `make dfu` in the `Firmware` directory. From 9b641e326e75cfd3561bc16dfa83c99904b6b4a1 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 24 Apr 2018 12:20:08 -0700 Subject: [PATCH 122/215] [firmware] disable DFU feature for board version <= 3.4 because it can break the board --- Firmware/Board/v3/Src/main.c | 33 ++++++++++++++++++------------ Firmware/MotorControl/commands.cpp | 15 ++++++++++++-- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/Firmware/Board/v3/Src/main.c b/Firmware/Board/v3/Src/main.c index 20f4ba8a..7b9d2c6c 100644 --- a/Firmware/Board/v3/Src/main.c +++ b/Firmware/Board/v3/Src/main.c @@ -100,19 +100,26 @@ int main(void) { /* USER CODE BEGIN 1 */ - /* - * This wait loop works around an obscure timing issue. - * When the transition NVIC_SystemReset() => STM bootloader happens quickly, - * there is a yet unexplained phenomenon where both the high side and low side - * brake resistor FETs would turn on simultaneously for about 2.5ms. - * This manifests in an audible click and may lead to failure of the FETs. - * When adding a delay before entering DFU mode the issue does not occur. - * - * This loop takes about 5 cycles per iteration, so the delay - * is about 1/168000kHz*5*1000000 = 30ms - */ - for (size_t i = 0; i < 1000000; ++i) { - __NOP(); + if(*((unsigned long *)0x2001C000) == 0xDEADFE75) { + /* The STM DFU bootloader enables internal pull-up resistors on PB10 (AUX_H) + * and PB11 (AUX_L), thereby causing shoot-through on the brake resistor + * FETs and obliterating them unless external 3.3k pull-down resistors are + * present. Pull-downs are only present on ODrive 3.5 or newer. + * On older boards we disable DFU by default but if the user insists + * there's only one thing left that might save it: time. + * The brake resistor gate driver needs a certain 10V supply (GVDD) to + * make it work. This voltage is supplied by the motor gate drivers which get + * disabled at system reset. So over time GVDD voltage _should_ below + * dangerous levels. This is completely handwavy and should not be relied on + * so you are on your own on if you ignore this warning. + * + * This loop takes 5 cycles per iteration and at this point the system runs + * on the internal 16MHz RC oscillator so the delay is about 2 seconds. + */ + for (size_t i = 0; i < (16000000UL / 5UL * 2UL); ++i) { + __NOP(); + } + *((unsigned long *)0x2001C000) = 0xDEADBEEF; } /* We could jump to the bootloader directly on demand without rebooting diff --git a/Firmware/MotorControl/commands.cpp b/Firmware/MotorControl/commands.cpp index a163ae4c..ced0574d 100644 --- a/Firmware/MotorControl/commands.cpp +++ b/Firmware/MotorControl/commands.cpp @@ -109,8 +109,19 @@ void motors_run_anticogging_calibration_func() { } void enter_dfu_mode() { - *((unsigned long *)0x2001C000) = 0xDEADBEEF; - NVIC_SystemReset(); + if ((HW_VERSION_MAJOR == 3) && (HW_VERSION_MINOR >= 5)) { + *((unsigned long *)0x2001C000) = 0xDEADBEEF; + NVIC_SystemReset(); + } else { + /* + * DFU mode is only allowed on board version >= 3.5 because it can burn + * the brake resistor FETs on older boards. + * If you really want to use it on an older board, add 3.3k pull-down resistors + * to the AUX_L and AUX_H signals and _only then_ uncomment these lines. + */ + //*((unsigned long *)0x2001C000) = 0xDEADFE75; + //NVIC_SystemReset(); + } } // This table specifies which fields and functions are exposed on the USB and UART ports. From e46040a29b6b7bd0a3a1f59a949ad5cc7dc1298f Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Tue, 24 Apr 2018 12:54:03 -0700 Subject: [PATCH 123/215] DFU not available for v3.4 or earlier --- Firmware/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Firmware/README.md b/Firmware/README.md index 2acd6474..5306901b 100644 --- a/Firmware/README.md +++ b/Firmware/README.md @@ -49,7 +49,7 @@ __CONFIG_STEP_DIR__: Set to `y` to use the GPIO1 and GPIO2 for step/direction in

## Downloading and Installing Tools ### Getting a programmer -__Note:__ If you don't plan to make major firmware modifications you can use the built-in DFU feature. +__Note:__ If you have ODrive v3.5 and newer, and don't plan to make major firmware modifications you can use the built-in DFU feature. In this case you don't need an SWD programmer and you can skip OpenOCD related instructions. Get a programmer that supports SWD (Serial Wire Debugging) and is ST-link v2 compatible. You can get them really cheap on [eBay](http://www.ebay.co.uk/itm/ST-Link-V2-Emulator-Downloader-Programming-Mini-Unit-STM8-STM32-with-20CM-Line-/391173940927?hash=item5b13c8a6bf:g:3g8AAOSw~OdVf-Tu) or many other places. @@ -96,7 +96,7 @@ After installing all of the above, open a Git Bash shell. Continue at section [B * Run `make` in the `Firmware` directory. ### Flashing the firmware (standalone device) -Note: ODrive v3.4 and earlier require you to flash with the external programmer first (see below), before you can reflash in standalone mode. +Note: This method of updating the firmware is only supported on ODrive v3.5 and newer. If you have an older board you must instead use the method in the [next section](#flashing-the-firmware). * __Windows__: Use the [Zadig](http://zadig.akeo.ie/) utility to set ODrive (not STLink!) driver to libusb-win32. * If 'Odrive version 3.x' is not in the list of devices upon opening Zadig, check 'List All Devices' from the options menu. With the Odrive selected in the device list choose 'libusb-win32' from the target driver list and select the large 'install driver' button. * Run `make dfu` in the `Firmware` directory. From 9ba52528257f97dd795eab42add08a9eff553e23 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Tue, 24 Apr 2018 13:11:37 -0700 Subject: [PATCH 124/215] invoke scripts with python command required on windows --- Firmware/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/Makefile b/Firmware/Makefile index 4b823b60..794b7695 100644 --- a/Firmware/Makefile +++ b/Firmware/Makefile @@ -24,7 +24,7 @@ gdb: all arm-none-eabi-gdb $(FIRMWARE) -x openocd.gdbinit dfu: all - ../tools/odrivetool $(if $(value SERIAL_NUMBER),--serial-number $(SERIAL_NUMBER),) dfu $(FIRMWARE_HEX) + python ../tools/odrivetool $(if $(value SERIAL_NUMBER),--serial-number $(SERIAL_NUMBER),) dfu $(FIRMWARE_HEX) bmp: all arm-none-eabi-gdb --ex 'target extended-remote /dev/stlink' \ From 70a86c7d5701e3cc6aebf5adec4602cf32a6e360 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 24 Apr 2018 13:16:38 -0700 Subject: [PATCH 125/215] disable dfu.py --- tools/dfu.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/dfu.py b/tools/dfu.py index 7ad27bf8..8a890507 100755 --- a/tools/dfu.py +++ b/tools/dfu.py @@ -14,6 +14,9 @@ import usb.core import usb.util import odrive.core +print("The DFU script had to be disabled because of it potentially breaking the board.") +sys.exit(1) + # We are interactively printing status messages, so flush by default import functools print = functools.partial(print, flush=True) From 49dcf47b37ec4a644aff3ea343886a518a44009a Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 24 Apr 2018 12:20:08 -0700 Subject: [PATCH 126/215] [firmware] disable DFU feature for board version <= 3.4 because it can break the board --- Firmware/Board/v3/Src/main.c | 33 ++++++++++++++++++------------ Firmware/MotorControl/commands.cpp | 21 ++++++++++++++----- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/Firmware/Board/v3/Src/main.c b/Firmware/Board/v3/Src/main.c index 20f4ba8a..1691e4a6 100644 --- a/Firmware/Board/v3/Src/main.c +++ b/Firmware/Board/v3/Src/main.c @@ -100,19 +100,26 @@ int main(void) { /* USER CODE BEGIN 1 */ - /* - * This wait loop works around an obscure timing issue. - * When the transition NVIC_SystemReset() => STM bootloader happens quickly, - * there is a yet unexplained phenomenon where both the high side and low side - * brake resistor FETs would turn on simultaneously for about 2.5ms. - * This manifests in an audible click and may lead to failure of the FETs. - * When adding a delay before entering DFU mode the issue does not occur. - * - * This loop takes about 5 cycles per iteration, so the delay - * is about 1/168000kHz*5*1000000 = 30ms - */ - for (size_t i = 0; i < 1000000; ++i) { - __NOP(); + if(*((unsigned long *)0x2001C000) == 0xDEADFE75) { + /* The STM DFU bootloader enables internal pull-up resistors on PB10 (AUX_H) + * and PB11 (AUX_L), thereby causing shoot-through on the brake resistor + * FETs and obliterating them unless external 3.3k pull-down resistors are + * present. Pull-downs are only present on ODrive 3.5 or newer. + * On older boards we disable DFU by default but if the user insists + * there's only one thing left that might save it: time. + * The brake resistor gate driver needs a certain 10V supply (GVDD) to + * make it work. This voltage is supplied by the motor gate drivers which get + * disabled at system reset. So over time GVDD voltage _should_ below + * dangerous levels. This is completely handwavy and should not be relied on + * so you are on your own on if you ignore this warning. + * + * This loop takes 5 cycles per iteration and at this point the system runs + * on the internal 16MHz RC oscillator so the delay is about 2 seconds. + */ + for (size_t i = 0; i < (16000000UL / 5UL * 2UL); ++i) { + __NOP(); + } + *((unsigned long *)0x2001C000) == 0xDEADBEEF; } /* We could jump to the bootloader directly on demand without rebooting diff --git a/Firmware/MotorControl/commands.cpp b/Firmware/MotorControl/commands.cpp index 4f8453d8..2ca2a066 100644 --- a/Firmware/MotorControl/commands.cpp +++ b/Firmware/MotorControl/commands.cpp @@ -109,11 +109,6 @@ void motors_run_anticogging_calibration_func() { } } -void enter_dfu_mode() { - *((unsigned long *)0x2001C000) = 0xDEADBEEF; - NVIC_SystemReset(); -} - #if HW_VERSION_MAJOR == 3 // Determine start address of the OTP struct: // The OTP is organized into 16-byte blocks. @@ -142,6 +137,22 @@ const uint8_t fw_version_minor = FW_VERSION_MINOR; const uint8_t fw_version_revision = FW_VERSION_REVISION; const uint8_t fw_version_unreleased = FW_VERSION_UNRELEASED; // 0 for official releases, 1 otherwise +void enter_dfu_mode() { + if ((board_version_major == 3) && (board_version_minor >= 5)) { + *((unsigned long *)0x2001C000) = 0xDEADBEEF; + NVIC_SystemReset(); + } else { + /* + * DFU mode is only allowed on board version >= 3.5 because it can burn + * the brake resistor FETs on older boards. + * If you really want to use it on an older board, add 3.3k pull-down resistors + * to the AUX_L and AUX_H signals and _only then_ uncomment these lines. + */ + //*((unsigned long *)0x2001C000) = 0xDEADFE75; + //NVIC_SystemReset(); + } +} + // This table specifies which fields and functions are exposed on the USB and UART ports. // TODO: Autogenerate this table. It will come up again very soon in the Arduino library. // clang-format off From 3c28ed6958a41a8ff5cffcb32c662f2068af2e0c Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 24 Apr 2018 12:32:33 -0700 Subject: [PATCH 127/215] [odrivetool] disable DFU feature for board version <= 3.4 because it can break the board --- tools/odrive/dfu.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/tools/odrive/dfu.py b/tools/odrive/dfu.py index d1b59f82..1efd4592 100755 --- a/tools/odrive/dfu.py +++ b/tools/odrive/dfu.py @@ -206,7 +206,7 @@ def show_deferred_message(message, cancellation_token): t.daemon = True t.start() -def put_odrive_into_dfu_mode(my_drive): +def put_odrive_into_dfu_mode(my_drive, cancellation_token): """ Puts the specified device into DFU mode """ @@ -216,15 +216,23 @@ def put_odrive_into_dfu_mode(my_drive): "DFU with this script should work fine." .format(my_drive.__channel__.usb_device.serial_number)) return - print("Putting device {} into DFU mode...".format(my_drive.__channel__.usb_device.serial_number)) - try: - my_drive.enter_dfu_mode() - except odrive.protocol.ChannelBrokenException as ex: - pass # this is expected because the device reboots - if platform.system() == "Windows": - show_deferred_message("Still waiting for the device to reappear.\n" - "Use the Zadig utility to set the driver of 'STM32 BOOTLOADER' to libusb-win32.", - find_odrive_cancellation_token) + hw_version_major = my_drive.hw_version_major if hasattr(my_drive, 'hw_version_major') else 3 + hw_version_minor = my_drive.hw_version_minor if hasattr(my_drive, 'hw_version_minor') else 4 + if hw_version_major == 3 and hw_version_minor >= 5: + print("Putting device {} into DFU mode...".format(my_drive.__channel__.usb_device.serial_number)) + try: + my_drive.enter_dfu_mode() + except odrive.protocol.ChannelBrokenException: + pass # this is expected because the device reboots + if platform.system() == "Windows": + show_deferred_message("Still waiting for the device to reappear.\n" + "Use the Zadig utility to set the driver of 'STM32 BOOTLOADER' to libusb-win32.", + cancellation_token) + else: + print("Found device {}".format(my_drive.__channel__.usb_device.serial_number)) + print(" DFU mode is not supported on board version 3.4 or earlier.") + print(" This is because entering DFU mode on such a device would") + print(" break the brake resistor FETs under some circumstances.") def launch_dfu(args, app_shutdown_token): """ @@ -251,7 +259,9 @@ def launch_dfu(args, app_shutdown_token): # Scan for ODrives not in DFU mode and put them into DFU mode once they appear # We only scan on USB because DFU is only possible over USB - odrive.discovery.find_all(args.path, serial_number, put_odrive_into_dfu_mode, find_odrive_cancellation_token, app_shutdown_token) + odrive.discovery.find_all(args.path, serial_number, + lambda dev: put_odrive_into_dfu_mode(dev, find_odrive_cancellation_token), + find_odrive_cancellation_token, app_shutdown_token) # Poll libUSB until a device in DFU mode is found while not app_shutdown_token.is_set(): From 7e928757806151888e3eaf970d0dd0e708bd836b Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 24 Apr 2018 13:21:58 -0700 Subject: [PATCH 128/215] fix compiler warning --- Firmware/Board/v3/Src/main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/Board/v3/Src/main.c b/Firmware/Board/v3/Src/main.c index 1691e4a6..7b9d2c6c 100644 --- a/Firmware/Board/v3/Src/main.c +++ b/Firmware/Board/v3/Src/main.c @@ -119,7 +119,7 @@ int main(void) for (size_t i = 0; i < (16000000UL / 5UL * 2UL); ++i) { __NOP(); } - *((unsigned long *)0x2001C000) == 0xDEADBEEF; + *((unsigned long *)0x2001C000) = 0xDEADBEEF; } /* We could jump to the bootloader directly on demand without rebooting From e2c14c00829f87bfc586ec123e9b09d4a3eb43ea Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 24 Apr 2018 13:49:39 -0700 Subject: [PATCH 129/215] fix excessive firmware size due to oscilloscope array --- Firmware/MotorControl/odrive_main.h | 3 ++- Firmware/communication/communication.cpp | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index bddf7e88..2b29b6ac 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -70,7 +70,8 @@ class Motor; constexpr size_t AXIS_COUNT = 2; extern Axis *axes[AXIS_COUNT]; -#define OSCILLOSCOPE_SIZE 18000 +// if you use the oscilloscope feature you can bump up this value +#define OSCILLOSCOPE_SIZE 128 extern float oscilloscope[OSCILLOSCOPE_SIZE]; extern size_t oscilloscope_pos; diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 86891a8f..ae611f24 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -76,9 +76,7 @@ void init_communication(void) { } -float oscilloscope[OSCILLOSCOPE_SIZE] = { - 0.123f, 0.345f, 0.4576f, 1.543f, -50.0f -}; +float oscilloscope[OSCILLOSCOPE_SIZE] = {0}; size_t oscilloscope_pos = 0; From 2fde5b4d857e9f783d0e829ccd4898559ddf8a80 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 24 Apr 2018 15:35:10 -0700 Subject: [PATCH 130/215] improve cross platform compatibility - python odrive module can be used from python2 again - the automatic firmware version.h generation is now based on python instead of bash (which didn't work well on Windows) --- Firmware/Tupfile.lua | 2 +- Firmware/dump_version.sh | 47 --------------------------- tools/odrive/__init__.py | 6 ++-- tools/odrive/utils.py | 41 ++---------------------- tools/odrive/version.py | 69 +++++++++++++++++++++++++--------------- tools/odrivetool | 3 +- tools/run_tests.py | 40 ++++++++++++++++++++++- tools/setup.py | 2 +- 8 files changed, 92 insertions(+), 118 deletions(-) delete mode 100755 Firmware/dump_version.sh diff --git a/Firmware/Tupfile.lua b/Firmware/Tupfile.lua index feae2670..ff95f0d1 100644 --- a/Firmware/Tupfile.lua +++ b/Firmware/Tupfile.lua @@ -138,7 +138,7 @@ build{ } tup.frule{ - command='bash dump_version.sh %o', + command='python ../tools/odrive/version.py --output %o', outputs={'build/version.h'} } diff --git a/Firmware/dump_version.sh b/Firmware/dump_version.sh deleted file mode 100755 index cdaaa372..00000000 --- a/Firmware/dump_version.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -set -euo pipefail - -if [ $# -eq 1 ]; then - OUTPUT="$1" -else - OUTPUT="/dev/stdout" -fi - -# The git root lies outside of the tup root -export GIT_DISCOVERY_ACROSS_FILESYSTEM=1 - -# Get a description of the current Git state -# Examples of what this string may become: -# fw-v0.3.6 The current commit is exactly at tag "fw-v0.3.6" -# There may or may not be untracked files in the -# working directory. -# fw-v0.3.6* The current commit is at tag "fw-v0.3.6" and there -# are uncommitted changes in the working directory. -# fw-v0.3.6-4-g3703ae5 The working directory at a commit with hash 3703ae5, -# 4 commits ahead of tag fw-v0.3.6 and clean. -FW_VERSION="$(git describe --always --tags --dirty=* || echo "[unknown commit]")" - -# Extract version numbers -FW_VERSION_MAJOR="$(sed -n 's/.*v\([0-9a-zA-Z]\).\([0-9a-zA-Z]\).\([0-9a-zA-Z]\)\(.*\)/\1/p' <<< "$FW_VERSION")" -FW_VERSION_MINOR="$(sed -n 's/.*v\([0-9a-zA-Z]\).\([0-9a-zA-Z]\).\([0-9a-zA-Z]\)\(.*\)/\2/p' <<< "$FW_VERSION")" -FW_VERSION_REVISION="$(sed -n 's/.*v\([0-9a-zA-Z]\).\([0-9a-zA-Z]\).\([0-9a-zA-Z]\)\(.*\)/\3/p' <<< "$FW_VERSION")" -FW_VERSION_SUFFIX="$(sed -n 's/.*v\([0-9a-zA-Z]\).\([0-9a-zA-Z]\).\([0-9a-zA-Z]\)\(.*\)/\4/p' <<< "$FW_VERSION")" - -# Fall back to 0 if the verions does not match the expected pattern -[ "$FW_VERSION_MAJOR" == "" ] && FW_VERSION_MAJOR=0 -[ "$FW_VERSION_MINOR" == "" ] && FW_VERSION_MINOR=0 -[ "$FW_VERSION_REVISION" == "" ] && FW_VERSION_REVISION=0 - -if [ "$FW_VERSION_SUFFIX" == "" ]; then - FW_VERSION_UNRELEASED=0 -else - FW_VERSION_UNRELEASED=1 -fi - -cat > "$OUTPUT" < 1: - msg = "task {} and {} failed.".format( - tracebacks[0][0], - "one other" if len(tracebacks) == 2 else str(len(tracebacks)-1) + " others" - ) - raise Exception(msg) from tracebacks[0][1] - - class Logger(): """ Logs messages to stdout diff --git a/tools/odrive/version.py b/tools/odrive/version.py index 327c915c..68bad7d0 100644 --- a/tools/odrive/version.py +++ b/tools/odrive/version.py @@ -4,7 +4,29 @@ import subprocess import os import sys -def get_version(git_only=False): +def get_version_from_git(): + script_dir = os.path.dirname(os.path.realpath(__file__)) + try: + # Determine the current git commit version + git_tag = subprocess.check_output(["git", "describe", "--always", "--tags", "--dirty=*"], + cwd=script_dir) + git_tag = git_tag.decode(sys.stdout.encoding).rstrip('\n') + + regex=r'.*v([0-9a-zA-Z]).([0-9a-zA-Z]).([0-9a-zA-Z])(.*)' + package_version_major = int(re.sub(regex, r"\1", git_tag)) + package_version_minor = int(re.sub(regex, r"\2", git_tag)) + package_version_revision = int(re.sub(regex, r"\3", git_tag)) + package_version_unreleased = (re.sub(regex, r"\4", git_tag) != "") + + if package_version_unreleased: + package_version_revision += 1 + + except Exception as ex: + print(ex) + return "[unknown version]", 0, 0, 0, 1 + return git_tag, package_version_major, package_version_minor, package_version_revision, package_version_unreleased + +def get_version_str(git_only=False): """ Returns the versions of the tools If git_only is true, the version.txt file is ignored even @@ -18,29 +40,24 @@ def get_version(git_only=False): if os.path.exists(version_file_path) and git_only == False: with open(version_file_path) as version_file: return version_file.readline().rstrip('\n') - - try: - # Determine the current git commit version - git_result = subprocess.run(["git", "describe", "--always", "--tags", "--dirty=*"], - cwd=script_dir, - stdout=subprocess.PIPE, timeout=10) - git_tag = git_result.stdout.decode(sys.stdout.encoding) - - regex=r'.*v([0-9a-zA-Z]).([0-9a-zA-Z]).([0-9a-zA-Z])(.*)' - package_version_major = int(re.sub(regex, r"\1", git_tag)) - package_version_minor = int(re.sub(regex, r"\2", git_tag)) - package_version_revision = int(re.sub(regex, r"\3", git_tag)) - package_version_unreleased = (re.sub(regex, r"\4", git_tag) != "") - - if package_version_unreleased: - package_version_revision += 1 - - # TODO: fetch from Git describe - version = '{}.{}.{}'.format(package_version_major, package_version_minor, package_version_revision) - - if package_version_unreleased: - version += ".dev" - except Exception as ex: - print(ex) - version = "whatever version in " + script_dir + + _, major, minor, revision, unreleased = get_version_from_git() + version = '{}.{}.{}'.format(major, minor, revision) + if unreleased: + version += ".dev" return version + +if __name__ == '__main__': + import argparse + parser = argparse.ArgumentParser(description='Version Dump\n') + parser.add_argument("--output", type=argparse.FileType('w'), default='-', + help="C header output file") + + args = parser.parse_args() + + git_name, major, minor, revision, unreleased = get_version_from_git() + args.output.write('#define FW_VERSION "{}"\n'.format(git_name)) + args.output.write('#define FW_VERSION_MAJOR {}\n'.format(major)) + args.output.write('#define FW_VERSION_MINOR {}\n'.format(minor)) + args.output.write('#define FW_VERSION_REVISION {}\n'.format(revision)) + args.output.write('#define FW_VERSION_UNRELEASED {}\n'.format(1 if unreleased else 0)) diff --git a/tools/odrivetool b/tools/odrivetool index a6e80acf..901ebde3 100755 --- a/tools/odrivetool +++ b/tools/odrivetool @@ -86,7 +86,8 @@ else: logger = Logger(verbose=args.verbose) def print_version(): - print("ODrive control utility v" + odrive.__version__) + sys.stderr.write("ODrive control utility v" + odrive.__version__ + "\n") + sys.stderr.flush() app_shutdown_token = Event() diff --git a/tools/run_tests.py b/tools/run_tests.py index f2f67a19..1cb1bcd8 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -14,7 +14,45 @@ import threading import traceback import argparse from odrive.tests import * -from odrive.utils import Logger, for_all_parallel, Event +from odrive.utils import Logger, Event + + +def for_all_parallel(objects, get_name, callback): + """ + Executes the specified callback for every object in the objects + list concurrently. This function waits for all callbacks to + finish and throws an exception if any of the callbacks throw + an exception. + """ + tracebacks = [] + + def run_callback(element): + try: + callback(element) + except Exception as ex: + tracebacks.append((get_name(element), ex)) + + # Start a thread for each element in the list + all_threads = [] + for element in objects: + thread = threading.Thread(target=run_callback, args=(element,)) + thread.start() + all_threads.append(thread) + + # Wait for all threads to complete + for thread in all_threads: + thread.join() + + if len(tracebacks) == 1: + msg = "task {} failed.".format(tracebacks[0][0]) + raise Exception(msg) from tracebacks[0][1] + elif len(tracebacks) > 1: + msg = "task {} and {} failed.".format( + tracebacks[0][0], + "one other" if len(tracebacks) == 2 else str(len(tracebacks)-1) + " others" + ) + raise Exception(msg) from tracebacks[0][1] + script_path=os.path.dirname(os.path.realpath(__file__)) diff --git a/tools/setup.py b/tools/setup.py index 946e6900..3cdb67d6 100644 --- a/tools/setup.py +++ b/tools/setup.py @@ -40,7 +40,7 @@ creating_package = "sdist" in sys.argv # Load version from Git tag import odrive.version -version = odrive.version.get_version(git_only=creating_package) +version = odrive.version.get_version_str(git_only=creating_package) # Change this if you already uploaded the current # version but need to release a hotfix From f5b4ab455503036e76f5b0c3f2050c2299499edd Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Tue, 24 Apr 2018 15:37:41 -0700 Subject: [PATCH 131/215] update mac run instructions --- Firmware/README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Firmware/README.md b/Firmware/README.md index 5306901b..8fef2b05 100644 --- a/Firmware/README.md +++ b/Firmware/README.md @@ -73,9 +73,10 @@ To compile the program, you first need to install the prerequisite tools: * No additional USB CDC driver should be required on Linux. #### Mac: -* `brew cask install gcc-arm-embedded`: GCC toolchain+debugger -* `brew cask install osxfuse; brew install tup`: Build tool -* `brew install openocd`: Programmer +First install [Homebrew](https://brew.sh/). Then you can run these commands in Terminal: +* `brew cask install gcc-arm-embedded`: to install GCC toolchain+debugger +* `brew cask install osxfuse; brew install tup`: to install the build tool +* `brew install openocd`: to install the programmer tool #### Windows: Install the following: @@ -165,7 +166,8 @@ pip install pyusb pyserial 5. __Windows__: Use the [Zadig](http://zadig.akeo.ie/) utility to set ODrive (not STLink!) driver to libusb-win32. * If 'Odrive version 3.x' is not in the list of devices upon opening Zadig, check 'List All Devices' from the options menu. With the Odrive selected in the device list choose 'libusb-win32' from the target driver list and select the large 'install driver' button. 6. Open the bash prompt in the `ODrive/tools/` folder. -7. Run `python3 demo.py` or `python3 explore_odrive.py`. +7. Run `python3 demo.py` or `python3 explore_odrive.py`. +- __Mac__: `python3 demo.py --discover serial` or `python3 explore_odrive.py --discover serial` - `demo.py` is a very simple script which will make motor 0 turn back and forth. Use this as an example if you want to control the ODrive yourself programatically. - `explore_odrive.py` drops you into an interactive python shell where you can explore and edit the parameters that are available on your device. For instance `my_odrive.motor0.pos_setpoint = 10000` makes motor0 move to position 10000. To connect over serial instead of USB run `./tools/explore_odrive.py --discover serial`. From 968131185232125ba64ee39512ab1e52c804b91c Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Tue, 24 Apr 2018 15:39:30 -0700 Subject: [PATCH 132/215] update mac run instructions --- Firmware/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/README.md b/Firmware/README.md index 8fef2b05..10a1051e 100644 --- a/Firmware/README.md +++ b/Firmware/README.md @@ -167,7 +167,7 @@ pip install pyusb pyserial * If 'Odrive version 3.x' is not in the list of devices upon opening Zadig, check 'List All Devices' from the options menu. With the Odrive selected in the device list choose 'libusb-win32' from the target driver list and select the large 'install driver' button. 6. Open the bash prompt in the `ODrive/tools/` folder. 7. Run `python3 demo.py` or `python3 explore_odrive.py`. -- __Mac__: `python3 demo.py --discover serial` or `python3 explore_odrive.py --discover serial` +- __Mac__: instead run: `python3 demo.py --discover serial` or `python3 explore_odrive.py --discover serial` - `demo.py` is a very simple script which will make motor 0 turn back and forth. Use this as an example if you want to control the ODrive yourself programatically. - `explore_odrive.py` drops you into an interactive python shell where you can explore and edit the parameters that are available on your device. For instance `my_odrive.motor0.pos_setpoint = 10000` makes motor0 move to position 10000. To connect over serial instead of USB run `./tools/explore_odrive.py --discover serial`. From 2bdb677ccf14307e5450ef67e2651c6f5852f696 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Tue, 24 Apr 2018 15:37:41 -0700 Subject: [PATCH 133/215] update mac run instructions --- Firmware/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Firmware/README.md b/Firmware/README.md index d4bd4ab9..e6180dec 100644 --- a/Firmware/README.md +++ b/Firmware/README.md @@ -73,9 +73,10 @@ To compile the program, you first need to install the prerequisite tools: * No additional USB CDC driver should be required on Linux. #### Mac: -* `brew cask install gcc-arm-embedded`: GCC toolchain+debugger -* `brew cask install osxfuse; brew install tup`: Build tool -* `brew install openocd`: Programmer +First install [Homebrew](https://brew.sh/). Then you can run these commands in Terminal: +* `brew cask install gcc-arm-embedded`: to install GCC toolchain+debugger +* `brew cask install osxfuse; brew install tup`: to install the build tool +* `brew install openocd`: to install the programmer tool #### Windows: Install the following: @@ -167,6 +168,7 @@ pip install pyusb pyserial * If 'Odrive version 3.x' is not in the list of devices upon opening Zadig, check 'List All Devices' from the options menu. With the Odrive selected in the device list choose 'libusb-win32' from the target driver list and select the large 'install driver' button. 6. Open the bash prompt in the `ODrive/tools/` folder. 7. Run `python3 odrive_demo.py` or `python3 odrivetool`. +- __Mac__: `python3 odrive_demo.py --discover serial` or `python3 explore_odrive.py --discover serial` - `odrive_demo.py` is a very simple script which will make motor 0 turn back and forth. Use this as an example if you want to control the ODrive yourself programatically. - `odrivetool` drops you into an interactive python shell when started without any arguments. There you can explore and edit the parameters that are available on your device. For instance `odrv0.motor0.pos_setpoint = 10000` makes motor0 move to position 10000. To connect over serial instead of USB run `./tools/odrivetool --path serial`. Run `./tools/odrivetool --help` to see what else you can do with the script. From 6064bfc7cc170adeb4018de9776a965e6a071458 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Tue, 24 Apr 2018 15:39:30 -0700 Subject: [PATCH 134/215] update mac run instructions --- Firmware/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/README.md b/Firmware/README.md index e6180dec..6937629c 100644 --- a/Firmware/README.md +++ b/Firmware/README.md @@ -168,7 +168,7 @@ pip install pyusb pyserial * If 'Odrive version 3.x' is not in the list of devices upon opening Zadig, check 'List All Devices' from the options menu. With the Odrive selected in the device list choose 'libusb-win32' from the target driver list and select the large 'install driver' button. 6. Open the bash prompt in the `ODrive/tools/` folder. 7. Run `python3 odrive_demo.py` or `python3 odrivetool`. -- __Mac__: `python3 odrive_demo.py --discover serial` or `python3 explore_odrive.py --discover serial` +- __Mac__: instead run: `python3 odrive_demo.py --discover serial` or `python3 explore_odrive.py --discover serial` - `odrive_demo.py` is a very simple script which will make motor 0 turn back and forth. Use this as an example if you want to control the ODrive yourself programatically. - `odrivetool` drops you into an interactive python shell when started without any arguments. There you can explore and edit the parameters that are available on your device. For instance `odrv0.motor0.pos_setpoint = 10000` makes motor0 move to position 10000. To connect over serial instead of USB run `./tools/odrivetool --path serial`. Run `./tools/odrivetool --help` to see what else you can do with the script. From 38998416fc6cb725c40fb63f60db1645cf4a0555 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 16 Apr 2018 14:27:53 -0700 Subject: [PATCH 135/215] enable I2C HAL in CubeMX (with DMA and EVT IRQ) --- .../Inc/stm32f4xx_hal_i2c.h | 649 ++ .../Inc/stm32f4xx_hal_i2c_ex.h | 137 + .../Src/stm32f4xx_hal_i2c.c | 5494 +++++++++++++++++ .../Src/stm32f4xx_hal_i2c_ex.c | 204 + Firmware/Board/v3/Inc/i2c.h | 91 + Firmware/Board/v3/Inc/main.h | 1 + Firmware/Board/v3/Inc/stm32f4xx_hal_conf.h | 2 +- Firmware/Board/v3/Inc/stm32f4xx_it.h | 3 + Firmware/Board/v3/Makefile | 5 +- Firmware/Board/v3/Odrive.ioc | 49 +- Firmware/Board/v3/Src/dma.c | 6 + Firmware/Board/v3/Src/i2c.c | 198 + Firmware/Board/v3/Src/main.c | 2 + Firmware/Board/v3/Src/stm32f4xx_it.c | 59 + 14 files changed, 6892 insertions(+), 8 deletions(-) create mode 100644 Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c.h create mode 100644 Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c_ex.h create mode 100644 Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c.c create mode 100644 Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c_ex.c create mode 100644 Firmware/Board/v3/Inc/i2c.h create mode 100644 Firmware/Board/v3/Src/i2c.c diff --git a/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c.h b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c.h new file mode 100644 index 00000000..5452a507 --- /dev/null +++ b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c.h @@ -0,0 +1,649 @@ +/** + ****************************************************************************** + * @file stm32f4xx_hal_i2c.h + * @author MCD Application Team + * @brief Header file of I2C HAL module. + ****************************************************************************** + * @attention + * + *

© COPYRIGHT(c) 2017 STMicroelectronics

+ * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of STMicroelectronics nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************** + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __STM32F4xx_HAL_I2C_H +#define __STM32F4xx_HAL_I2C_H + +#ifdef __cplusplus + extern "C" { +#endif + +/* Includes ------------------------------------------------------------------*/ +#include "stm32f4xx_hal_def.h" + +/** @addtogroup STM32F4xx_HAL_Driver + * @{ + */ + +/** @addtogroup I2C + * @{ + */ + +/* Exported types ------------------------------------------------------------*/ +/** @defgroup I2C_Exported_Types I2C Exported Types + * @{ + */ + +/** + * @brief I2C Configuration Structure definition + */ +typedef struct +{ + uint32_t ClockSpeed; /*!< Specifies the clock frequency. + This parameter must be set to a value lower than 400kHz */ + + uint32_t DutyCycle; /*!< Specifies the I2C fast mode duty cycle. + This parameter can be a value of @ref I2C_duty_cycle_in_fast_mode */ + + uint32_t OwnAddress1; /*!< Specifies the first device own address. + This parameter can be a 7-bit or 10-bit address. */ + + uint32_t AddressingMode; /*!< Specifies if 7-bit or 10-bit addressing mode is selected. + This parameter can be a value of @ref I2C_addressing_mode */ + + uint32_t DualAddressMode; /*!< Specifies if dual addressing mode is selected. + This parameter can be a value of @ref I2C_dual_addressing_mode */ + + uint32_t OwnAddress2; /*!< Specifies the second device own address if dual addressing mode is selected + This parameter can be a 7-bit address. */ + + uint32_t GeneralCallMode; /*!< Specifies if general call mode is selected. + This parameter can be a value of @ref I2C_general_call_addressing_mode */ + + uint32_t NoStretchMode; /*!< Specifies if nostretch mode is selected. + This parameter can be a value of @ref I2C_nostretch_mode */ + +}I2C_InitTypeDef; + +/** + * @brief HAL State structure definition + * @note HAL I2C State value coding follow below described bitmap : + * b7-b6 Error information + * 00 : No Error + * 01 : Abort (Abort user request on going) + * 10 : Timeout + * 11 : Error + * b5 IP initilisation status + * 0 : Reset (IP not initialized) + * 1 : Init done (IP initialized and ready to use. HAL I2C Init function called) + * b4 (not used) + * x : Should be set to 0 + * b3 + * 0 : Ready or Busy (No Listen mode ongoing) + * 1 : Listen (IP in Address Listen Mode) + * b2 Intrinsic process state + * 0 : Ready + * 1 : Busy (IP busy with some configuration or internal operations) + * b1 Rx state + * 0 : Ready (no Rx operation ongoing) + * 1 : Busy (Rx operation ongoing) + * b0 Tx state + * 0 : Ready (no Tx operation ongoing) + * 1 : Busy (Tx operation ongoing) + */ +typedef enum +{ + HAL_I2C_STATE_RESET = 0x00U, /*!< Peripheral is not yet Initialized */ + HAL_I2C_STATE_READY = 0x20U, /*!< Peripheral Initialized and ready for use */ + HAL_I2C_STATE_BUSY = 0x24U, /*!< An internal process is ongoing */ + HAL_I2C_STATE_BUSY_TX = 0x21U, /*!< Data Transmission process is ongoing */ + HAL_I2C_STATE_BUSY_RX = 0x22U, /*!< Data Reception process is ongoing */ + HAL_I2C_STATE_LISTEN = 0x28U, /*!< Address Listen Mode is ongoing */ + HAL_I2C_STATE_BUSY_TX_LISTEN = 0x29U, /*!< Address Listen Mode and Data Transmission + process is ongoing */ + HAL_I2C_STATE_BUSY_RX_LISTEN = 0x2AU, /*!< Address Listen Mode and Data Reception + process is ongoing */ + HAL_I2C_STATE_ABORT = 0x60U, /*!< Abort user request ongoing */ + HAL_I2C_STATE_TIMEOUT = 0xA0U, /*!< Timeout state */ + HAL_I2C_STATE_ERROR = 0xE0U /*!< Error */ + +}HAL_I2C_StateTypeDef; + +/** + * @brief HAL Mode structure definition + * @note HAL I2C Mode value coding follow below described bitmap : + * b7 (not used) + * x : Should be set to 0 + * b6 + * 0 : None + * 1 : Memory (HAL I2C communication is in Memory Mode) + * b5 + * 0 : None + * 1 : Slave (HAL I2C communication is in Slave Mode) + * b4 + * 0 : None + * 1 : Master (HAL I2C communication is in Master Mode) + * b3-b2-b1-b0 (not used) + * xxxx : Should be set to 0000 + */ +typedef enum +{ + HAL_I2C_MODE_NONE = 0x00U, /*!< No I2C communication on going */ + HAL_I2C_MODE_MASTER = 0x10U, /*!< I2C communication is in Master Mode */ + HAL_I2C_MODE_SLAVE = 0x20U, /*!< I2C communication is in Slave Mode */ + HAL_I2C_MODE_MEM = 0x40U /*!< I2C communication is in Memory Mode */ + +}HAL_I2C_ModeTypeDef; + +/** + * @brief I2C handle Structure definition + */ +typedef struct +{ + I2C_TypeDef *Instance; /*!< I2C registers base address */ + + I2C_InitTypeDef Init; /*!< I2C communication parameters */ + + uint8_t *pBuffPtr; /*!< Pointer to I2C transfer buffer */ + + uint16_t XferSize; /*!< I2C transfer size */ + + __IO uint16_t XferCount; /*!< I2C transfer counter */ + + __IO uint32_t XferOptions; /*!< I2C transfer options */ + + __IO uint32_t PreviousState; /*!< I2C communication Previous state and mode + context for internal usage */ + + DMA_HandleTypeDef *hdmatx; /*!< I2C Tx DMA handle parameters */ + + DMA_HandleTypeDef *hdmarx; /*!< I2C Rx DMA handle parameters */ + + HAL_LockTypeDef Lock; /*!< I2C locking object */ + + __IO HAL_I2C_StateTypeDef State; /*!< I2C communication state */ + + __IO HAL_I2C_ModeTypeDef Mode; /*!< I2C communication mode */ + + __IO uint32_t ErrorCode; /*!< I2C Error code */ + + __IO uint32_t Devaddress; /*!< I2C Target device address */ + + __IO uint32_t Memaddress; /*!< I2C Target memory address */ + + __IO uint32_t MemaddSize; /*!< I2C Target memory address size */ + + __IO uint32_t EventCount; /*!< I2C Event counter */ + +}I2C_HandleTypeDef; + +/** + * @} + */ + +/* Exported constants --------------------------------------------------------*/ +/** @defgroup I2C_Exported_Constants I2C Exported Constants + * @{ + */ + +/** @defgroup I2C_Error_Code I2C Error Code + * @brief I2C Error Code + * @{ + */ +#define HAL_I2C_ERROR_NONE 0x00000000U /*!< No error */ +#define HAL_I2C_ERROR_BERR 0x00000001U /*!< BERR error */ +#define HAL_I2C_ERROR_ARLO 0x00000002U /*!< ARLO error */ +#define HAL_I2C_ERROR_AF 0x00000004U /*!< AF error */ +#define HAL_I2C_ERROR_OVR 0x00000008U /*!< OVR error */ +#define HAL_I2C_ERROR_DMA 0x00000010U /*!< DMA transfer error */ +#define HAL_I2C_ERROR_TIMEOUT 0x00000020U /*!< Timeout Error */ +/** + * @} + */ + +/** @defgroup I2C_duty_cycle_in_fast_mode I2C duty cycle in fast mode + * @{ + */ +#define I2C_DUTYCYCLE_2 0x00000000U +#define I2C_DUTYCYCLE_16_9 I2C_CCR_DUTY +/** + * @} + */ + +/** @defgroup I2C_addressing_mode I2C addressing mode + * @{ + */ +#define I2C_ADDRESSINGMODE_7BIT 0x00004000U +#define I2C_ADDRESSINGMODE_10BIT (I2C_OAR1_ADDMODE | 0x00004000U) +/** + * @} + */ + +/** @defgroup I2C_dual_addressing_mode I2C dual addressing mode + * @{ + */ +#define I2C_DUALADDRESS_DISABLE 0x00000000U +#define I2C_DUALADDRESS_ENABLE I2C_OAR2_ENDUAL +/** + * @} + */ + +/** @defgroup I2C_general_call_addressing_mode I2C general call addressing mode + * @{ + */ +#define I2C_GENERALCALL_DISABLE 0x00000000U +#define I2C_GENERALCALL_ENABLE I2C_CR1_ENGC +/** + * @} + */ + +/** @defgroup I2C_nostretch_mode I2C nostretch mode + * @{ + */ +#define I2C_NOSTRETCH_DISABLE 0x00000000U +#define I2C_NOSTRETCH_ENABLE I2C_CR1_NOSTRETCH +/** + * @} + */ + +/** @defgroup I2C_Memory_Address_Size I2C Memory Address Size + * @{ + */ +#define I2C_MEMADD_SIZE_8BIT 0x00000001U +#define I2C_MEMADD_SIZE_16BIT 0x00000010U +/** + * @} + */ + +/** @defgroup I2C_XferDirection_definition I2C XferDirection definition + * @{ + */ +#define I2C_DIRECTION_RECEIVE 0x00000000U +#define I2C_DIRECTION_TRANSMIT 0x00000001U +/** + * @} + */ + +/** @defgroup I2C_XferOptions_definition I2C XferOptions definition + * @{ + */ +#define I2C_FIRST_FRAME 0x00000001U +#define I2C_NEXT_FRAME 0x00000002U +#define I2C_FIRST_AND_LAST_FRAME 0x00000004U +#define I2C_LAST_FRAME 0x00000008U +/** + * @} + */ + +/** @defgroup I2C_Interrupt_configuration_definition I2C Interrupt configuration definition + * @{ + */ +#define I2C_IT_BUF I2C_CR2_ITBUFEN +#define I2C_IT_EVT I2C_CR2_ITEVTEN +#define I2C_IT_ERR I2C_CR2_ITERREN +/** + * @} + */ + +/** @defgroup I2C_Flag_definition I2C Flag definition + * @{ + */ +#define I2C_FLAG_SMBALERT 0x00018000U +#define I2C_FLAG_TIMEOUT 0x00014000U +#define I2C_FLAG_PECERR 0x00011000U +#define I2C_FLAG_OVR 0x00010800U +#define I2C_FLAG_AF 0x00010400U +#define I2C_FLAG_ARLO 0x00010200U +#define I2C_FLAG_BERR 0x00010100U +#define I2C_FLAG_TXE 0x00010080U +#define I2C_FLAG_RXNE 0x00010040U +#define I2C_FLAG_STOPF 0x00010010U +#define I2C_FLAG_ADD10 0x00010008U +#define I2C_FLAG_BTF 0x00010004U +#define I2C_FLAG_ADDR 0x00010002U +#define I2C_FLAG_SB 0x00010001U +#define I2C_FLAG_DUALF 0x00100080U +#define I2C_FLAG_SMBHOST 0x00100040U +#define I2C_FLAG_SMBDEFAULT 0x00100020U +#define I2C_FLAG_GENCALL 0x00100010U +#define I2C_FLAG_TRA 0x00100004U +#define I2C_FLAG_BUSY 0x00100002U +#define I2C_FLAG_MSL 0x00100001U +/** + * @} + */ + +/** + * @} + */ + +/* Exported macro ------------------------------------------------------------*/ +/** @defgroup I2C_Exported_Macros I2C Exported Macros + * @{ + */ + +/** @brief Reset I2C handle state + * @param __HANDLE__ specifies the I2C Handle. + * This parameter can be I2C where x: 1, 2, or 3 to select the I2C peripheral. + * @retval None + */ +#define __HAL_I2C_RESET_HANDLE_STATE(__HANDLE__) ((__HANDLE__)->State = HAL_I2C_STATE_RESET) + +/** @brief Enable or disable the specified I2C interrupts. + * @param __HANDLE__ specifies the I2C Handle. + * This parameter can be I2C where x: 1, 2, or 3 to select the I2C peripheral. + * @param __INTERRUPT__ specifies the interrupt source to enable or disable. + * This parameter can be one of the following values: + * @arg I2C_IT_BUF: Buffer interrupt enable + * @arg I2C_IT_EVT: Event interrupt enable + * @arg I2C_IT_ERR: Error interrupt enable + * @retval None + */ +#define __HAL_I2C_ENABLE_IT(__HANDLE__, __INTERRUPT__) ((__HANDLE__)->Instance->CR2 |= (__INTERRUPT__)) +#define __HAL_I2C_DISABLE_IT(__HANDLE__, __INTERRUPT__) ((__HANDLE__)->Instance->CR2 &= (~(__INTERRUPT__))) + +/** @brief Checks if the specified I2C interrupt source is enabled or disabled. + * @param __HANDLE__ specifies the I2C Handle. + * This parameter can be I2C where x: 1, 2, or 3 to select the I2C peripheral. + * @param __INTERRUPT__ specifies the I2C interrupt source to check. + * This parameter can be one of the following values: + * @arg I2C_IT_BUF: Buffer interrupt enable + * @arg I2C_IT_EVT: Event interrupt enable + * @arg I2C_IT_ERR: Error interrupt enable + * @retval The new state of __INTERRUPT__ (TRUE or FALSE). + */ +#define __HAL_I2C_GET_IT_SOURCE(__HANDLE__, __INTERRUPT__) ((((__HANDLE__)->Instance->CR2 & (__INTERRUPT__)) == (__INTERRUPT__)) ? SET : RESET) + +/** @brief Checks whether the specified I2C flag is set or not. + * @param __HANDLE__ specifies the I2C Handle. + * This parameter can be I2C where x: 1, 2, or 3 to select the I2C peripheral. + * @param __FLAG__ specifies the flag to check. + * This parameter can be one of the following values: + * @arg I2C_FLAG_SMBALERT: SMBus Alert flag + * @arg I2C_FLAG_TIMEOUT: Timeout or Tlow error flag + * @arg I2C_FLAG_PECERR: PEC error in reception flag + * @arg I2C_FLAG_OVR: Overrun/Underrun flag + * @arg I2C_FLAG_AF: Acknowledge failure flag + * @arg I2C_FLAG_ARLO: Arbitration lost flag + * @arg I2C_FLAG_BERR: Bus error flag + * @arg I2C_FLAG_TXE: Data register empty flag + * @arg I2C_FLAG_RXNE: Data register not empty flag + * @arg I2C_FLAG_STOPF: Stop detection flag + * @arg I2C_FLAG_ADD10: 10-bit header sent flag + * @arg I2C_FLAG_BTF: Byte transfer finished flag + * @arg I2C_FLAG_ADDR: Address sent flag + * Address matched flag + * @arg I2C_FLAG_SB: Start bit flag + * @arg I2C_FLAG_DUALF: Dual flag + * @arg I2C_FLAG_SMBHOST: SMBus host header + * @arg I2C_FLAG_SMBDEFAULT: SMBus default header + * @arg I2C_FLAG_GENCALL: General call header flag + * @arg I2C_FLAG_TRA: Transmitter/Receiver flag + * @arg I2C_FLAG_BUSY: Bus busy flag + * @arg I2C_FLAG_MSL: Master/Slave flag + * @retval The new state of __FLAG__ (TRUE or FALSE). + */ +#define __HAL_I2C_GET_FLAG(__HANDLE__, __FLAG__) ((((uint8_t)((__FLAG__) >> 16U)) == 0x01U)?((((__HANDLE__)->Instance->SR1) & ((__FLAG__) & I2C_FLAG_MASK)) == ((__FLAG__) & I2C_FLAG_MASK)): \ + ((((__HANDLE__)->Instance->SR2) & ((__FLAG__) & I2C_FLAG_MASK)) == ((__FLAG__) & I2C_FLAG_MASK))) + +/** @brief Clears the I2C pending flags which are cleared by writing 0 in a specific bit. + * @param __HANDLE__ specifies the I2C Handle. + * This parameter can be I2C where x: 1, 2, or 3 to select the I2C peripheral. + * @param __FLAG__ specifies the flag to clear. + * This parameter can be any combination of the following values: + * @arg I2C_FLAG_SMBALERT: SMBus Alert flag + * @arg I2C_FLAG_TIMEOUT: Timeout or Tlow error flag + * @arg I2C_FLAG_PECERR: PEC error in reception flag + * @arg I2C_FLAG_OVR: Overrun/Underrun flag (Slave mode) + * @arg I2C_FLAG_AF: Acknowledge failure flag + * @arg I2C_FLAG_ARLO: Arbitration lost flag (Master mode) + * @arg I2C_FLAG_BERR: Bus error flag + * @retval None + */ +#define __HAL_I2C_CLEAR_FLAG(__HANDLE__, __FLAG__) ((__HANDLE__)->Instance->SR1 = ~((__FLAG__) & I2C_FLAG_MASK)) + +/** @brief Clears the I2C ADDR pending flag. + * @param __HANDLE__ specifies the I2C Handle. + * This parameter can be I2C where x: 1, 2, or 3 to select the I2C peripheral. + * @retval None + */ +#define __HAL_I2C_CLEAR_ADDRFLAG(__HANDLE__) \ + do{ \ + __IO uint32_t tmpreg = 0x00U; \ + tmpreg = (__HANDLE__)->Instance->SR1; \ + tmpreg = (__HANDLE__)->Instance->SR2; \ + UNUSED(tmpreg); \ + } while(0) + +/** @brief Clears the I2C STOPF pending flag. + * @param __HANDLE__ specifies the I2C Handle. + * This parameter can be I2C where x: 1, 2, or 3 to select the I2C peripheral. + * @retval None + */ +#define __HAL_I2C_CLEAR_STOPFLAG(__HANDLE__) \ + do{ \ + __IO uint32_t tmpreg = 0x00U; \ + tmpreg = (__HANDLE__)->Instance->SR1; \ + (__HANDLE__)->Instance->CR1 |= I2C_CR1_PE; \ + UNUSED(tmpreg); \ + } while(0) + +/** @brief Enable the I2C peripheral. + * @param __HANDLE__ specifies the I2C Handle. + * This parameter can be I2Cx where x: 1 or 2 to select the I2C peripheral. + * @retval None + */ +#define __HAL_I2C_ENABLE(__HANDLE__) ((__HANDLE__)->Instance->CR1 |= I2C_CR1_PE) + +/** @brief Disable the I2C peripheral. + * @param __HANDLE__ specifies the I2C Handle. + * This parameter can be I2Cx where x: 1 or 2 to select the I2C peripheral. + * @retval None + */ +#define __HAL_I2C_DISABLE(__HANDLE__) ((__HANDLE__)->Instance->CR1 &= ~I2C_CR1_PE) + +/** + * @} + */ + +/* Include I2C HAL Extension module */ +#include "stm32f4xx_hal_i2c_ex.h" + +/* Exported functions --------------------------------------------------------*/ +/** @addtogroup I2C_Exported_Functions + * @{ + */ + +/** @addtogroup I2C_Exported_Functions_Group1 + * @{ + */ +/* Initialization/de-initialization functions **********************************/ +HAL_StatusTypeDef HAL_I2C_Init(I2C_HandleTypeDef *hi2c); +HAL_StatusTypeDef HAL_I2C_DeInit (I2C_HandleTypeDef *hi2c); +void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c); +void HAL_I2C_MspDeInit(I2C_HandleTypeDef *hi2c); +/** + * @} + */ + +/** @addtogroup I2C_Exported_Functions_Group2 + * @{ + */ +/* I/O operation functions *****************************************************/ +/******* Blocking mode: Polling */ +HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); +HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); +HAL_StatusTypeDef HAL_I2C_Slave_Transmit(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout); +HAL_StatusTypeDef HAL_I2C_Slave_Receive(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout); +HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout); +HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout); +HAL_StatusTypeDef HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout); + +/******* Non-Blocking mode: Interrupt */ +HAL_StatusTypeDef HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_I2C_Slave_Transmit_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_I2C_Slave_Receive_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_I2C_Mem_Write_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_I2C_Mem_Read_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size); + +HAL_StatusTypeDef HAL_I2C_Master_Sequential_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions); +HAL_StatusTypeDef HAL_I2C_Master_Sequential_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions); +HAL_StatusTypeDef HAL_I2C_Slave_Sequential_Transmit_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t XferOptions); +HAL_StatusTypeDef HAL_I2C_Slave_Sequential_Receive_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t XferOptions); +HAL_StatusTypeDef HAL_I2C_Master_Abort_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress); +HAL_StatusTypeDef HAL_I2C_EnableListen_IT(I2C_HandleTypeDef *hi2c); +HAL_StatusTypeDef HAL_I2C_DisableListen_IT(I2C_HandleTypeDef *hi2c); + +/******* Non-Blocking mode: DMA */ +HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_I2C_Slave_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_I2C_Slave_Receive_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_I2C_Mem_Write_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size); +HAL_StatusTypeDef HAL_I2C_Mem_Read_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size); + +/******* I2C IRQHandler and Callbacks used in non blocking modes (Interrupt and DMA) */ +void HAL_I2C_EV_IRQHandler(I2C_HandleTypeDef *hi2c); +void HAL_I2C_ER_IRQHandler(I2C_HandleTypeDef *hi2c); +void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c); +void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c); +void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c); +void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c); +void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode); +void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef *hi2c); +void HAL_I2C_MemTxCpltCallback(I2C_HandleTypeDef *hi2c); +void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c); +void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c); +void HAL_I2C_AbortCpltCallback(I2C_HandleTypeDef *hi2c); +/** + * @} + */ + +/** @addtogroup I2C_Exported_Functions_Group3 + * @{ + */ +/* Peripheral State, Mode and Errors functions *********************************/ +HAL_I2C_StateTypeDef HAL_I2C_GetState(I2C_HandleTypeDef *hi2c); +HAL_I2C_ModeTypeDef HAL_I2C_GetMode(I2C_HandleTypeDef *hi2c); +uint32_t HAL_I2C_GetError(I2C_HandleTypeDef *hi2c); + +/** + * @} + */ + +/** + * @} + */ +/* Private types -------------------------------------------------------------*/ +/* Private variables ---------------------------------------------------------*/ +/* Private constants ---------------------------------------------------------*/ +/** @defgroup I2C_Private_Constants I2C Private Constants + * @{ + */ +#define I2C_FLAG_MASK 0x0000FFFFU +/** + * @} + */ + +/* Private macros ------------------------------------------------------------*/ +/** @defgroup I2C_Private_Macros I2C Private Macros + * @{ + */ + +#define I2C_FREQRANGE(__PCLK__) ((__PCLK__)/1000000U) +#define I2C_RISE_TIME(__FREQRANGE__, __SPEED__) (((__SPEED__) <= 100000U) ? ((__FREQRANGE__) + 1U) : ((((__FREQRANGE__) * 300U) / 1000U) + 1U)) +#define I2C_SPEED_STANDARD(__PCLK__, __SPEED__) (((((__PCLK__)/((__SPEED__) << 1U)) & I2C_CCR_CCR) < 4U)? 4U:((__PCLK__) / ((__SPEED__) << 1U))) +#define I2C_SPEED_FAST(__PCLK__, __SPEED__, __DUTYCYCLE__) (((__DUTYCYCLE__) == I2C_DUTYCYCLE_2)? ((__PCLK__) / ((__SPEED__) * 3U)) : (((__PCLK__) / ((__SPEED__) * 25U)) | I2C_DUTYCYCLE_16_9)) +#define I2C_SPEED(__PCLK__, __SPEED__, __DUTYCYCLE__) (((__SPEED__) <= 100000U)? (I2C_SPEED_STANDARD((__PCLK__), (__SPEED__))) : \ + ((I2C_SPEED_FAST((__PCLK__), (__SPEED__), (__DUTYCYCLE__)) & I2C_CCR_CCR) == 0U)? 1U : \ + ((I2C_SPEED_FAST((__PCLK__), (__SPEED__), (__DUTYCYCLE__))) | I2C_CCR_FS)) + +#define I2C_7BIT_ADD_WRITE(__ADDRESS__) ((uint8_t)((__ADDRESS__) & (~I2C_OAR1_ADD0))) +#define I2C_7BIT_ADD_READ(__ADDRESS__) ((uint8_t)((__ADDRESS__) | I2C_OAR1_ADD0)) + +#define I2C_10BIT_ADDRESS(__ADDRESS__) ((uint8_t)((uint16_t)((__ADDRESS__) & (uint16_t)0x00FF))) +#define I2C_10BIT_HEADER_WRITE(__ADDRESS__) ((uint8_t)((uint16_t)((uint16_t)(((uint16_t)((__ADDRESS__) & (uint16_t)0x0300)) >> 7) | (uint16_t)0x00F0))) +#define I2C_10BIT_HEADER_READ(__ADDRESS__) ((uint8_t)((uint16_t)((uint16_t)(((uint16_t)((__ADDRESS__) & (uint16_t)0x0300)) >> 7) | (uint16_t)(0x00F1)))) + +#define I2C_MEM_ADD_MSB(__ADDRESS__) ((uint8_t)((uint16_t)(((uint16_t)((__ADDRESS__) & (uint16_t)0xFF00)) >> 8))) +#define I2C_MEM_ADD_LSB(__ADDRESS__) ((uint8_t)((uint16_t)((__ADDRESS__) & (uint16_t)0x00FF))) + +/** @defgroup I2C_IS_RTC_Definitions I2C Private macros to check input parameters + * @{ + */ +#define IS_I2C_DUTY_CYCLE(CYCLE) (((CYCLE) == I2C_DUTYCYCLE_2) || \ + ((CYCLE) == I2C_DUTYCYCLE_16_9)) +#define IS_I2C_ADDRESSING_MODE(ADDRESS) (((ADDRESS) == I2C_ADDRESSINGMODE_7BIT) || \ + ((ADDRESS) == I2C_ADDRESSINGMODE_10BIT)) +#define IS_I2C_DUAL_ADDRESS(ADDRESS) (((ADDRESS) == I2C_DUALADDRESS_DISABLE) || \ + ((ADDRESS) == I2C_DUALADDRESS_ENABLE)) +#define IS_I2C_GENERAL_CALL(CALL) (((CALL) == I2C_GENERALCALL_DISABLE) || \ + ((CALL) == I2C_GENERALCALL_ENABLE)) +#define IS_I2C_NO_STRETCH(STRETCH) (((STRETCH) == I2C_NOSTRETCH_DISABLE) || \ + ((STRETCH) == I2C_NOSTRETCH_ENABLE)) +#define IS_I2C_MEMADD_SIZE(SIZE) (((SIZE) == I2C_MEMADD_SIZE_8BIT) || \ + ((SIZE) == I2C_MEMADD_SIZE_16BIT)) +#define IS_I2C_CLOCK_SPEED(SPEED) (((SPEED) > 0U) && ((SPEED) <= 400000U)) +#define IS_I2C_OWN_ADDRESS1(ADDRESS1) (((ADDRESS1) & 0xFFFFFC00U) == 0U) +#define IS_I2C_OWN_ADDRESS2(ADDRESS2) (((ADDRESS2) & 0xFFFFFF01U) == 0U) +#define IS_I2C_TRANSFER_OPTIONS_REQUEST(REQUEST) (((REQUEST) == I2C_FIRST_FRAME) || \ + ((REQUEST) == I2C_NEXT_FRAME) || \ + ((REQUEST) == I2C_FIRST_AND_LAST_FRAME) || \ + ((REQUEST) == I2C_LAST_FRAME)) +/** + * @} + */ + +/** + * @} + */ + +/* Private functions ---------------------------------------------------------*/ +/** @defgroup I2C_Private_Functions I2C Private Functions + * @{ + */ + +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + + +#endif /* __STM32F4xx_HAL_I2C_H */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c_ex.h b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c_ex.h new file mode 100644 index 00000000..ff47d5cc --- /dev/null +++ b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c_ex.h @@ -0,0 +1,137 @@ +/** + ****************************************************************************** + * @file stm32f4xx_hal_i2c_ex.h + * @author MCD Application Team + * @brief Header file of I2C HAL Extension module. + ****************************************************************************** + * @attention + * + *

© COPYRIGHT(c) 2017 STMicroelectronics

+ * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of STMicroelectronics nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************** + */ + +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __STM32F4xx_HAL_I2C_EX_H +#define __STM32F4xx_HAL_I2C_EX_H + +#ifdef __cplusplus + extern "C" { +#endif + +#if defined(STM32F427xx) || defined(STM32F437xx) || defined(STM32F429xx) || defined(STM32F439xx) ||\ + defined(STM32F401xC) || defined(STM32F401xE) || defined(STM32F411xE) || defined(STM32F446xx) ||\ + defined(STM32F469xx) || defined(STM32F479xx) || defined(STM32F413xx) || defined(STM32F423xx) +/* Includes ------------------------------------------------------------------*/ +#include "stm32f4xx_hal_def.h" + +/** @addtogroup STM32F4xx_HAL_Driver + * @{ + */ + +/** @addtogroup I2CEx + * @{ + */ + +/* Exported types ------------------------------------------------------------*/ +/* Exported constants --------------------------------------------------------*/ +/** @defgroup I2CEx_Exported_Constants I2C Exported Constants + * @{ + */ + +/** @defgroup I2CEx_Analog_Filter I2C Analog Filter + * @{ + */ +#define I2C_ANALOGFILTER_ENABLE 0x00000000U +#define I2C_ANALOGFILTER_DISABLE I2C_FLTR_ANOFF +/** + * @} + */ + +/** + * @} + */ + +/* Exported macro ------------------------------------------------------------*/ +/* Exported functions --------------------------------------------------------*/ +/** @addtogroup I2CEx_Exported_Functions + * @{ + */ + +/** @addtogroup I2CEx_Exported_Functions_Group1 + * @{ + */ +/* Peripheral Control functions ************************************************/ +HAL_StatusTypeDef HAL_I2CEx_ConfigAnalogFilter(I2C_HandleTypeDef *hi2c, uint32_t AnalogFilter); +HAL_StatusTypeDef HAL_I2CEx_ConfigDigitalFilter(I2C_HandleTypeDef *hi2c, uint32_t DigitalFilter); +/** + * @} + */ + +/** + * @} + */ +/* Private types -------------------------------------------------------------*/ +/* Private variables ---------------------------------------------------------*/ +/* Private constants ---------------------------------------------------------*/ +/** @defgroup I2CEx_Private_Constants I2C Private Constants + * @{ + */ + +/** + * @} + */ + +/* Private macros ------------------------------------------------------------*/ +/** @defgroup I2CEx_Private_Macros I2C Private Macros + * @{ + */ +#define IS_I2C_ANALOG_FILTER(FILTER) (((FILTER) == I2C_ANALOGFILTER_ENABLE) || \ + ((FILTER) == I2C_ANALOGFILTER_DISABLE)) +#define IS_I2C_DIGITAL_FILTER(FILTER) ((FILTER) <= 0x0000000FU) +/** + * @} + */ + +/** + * @} + */ + +/** + * @} + */ + +#endif /* STM32F427xx || STM32F429xx || STM32F437xx || STM32F439xx || STM32F401xC ||\ + STM32F401xE || STM32F411xE || STM32F446xx || STM32F469xx || STM32F479xx ||\ + STM32F413xx || STM32F423xx */ + +#ifdef __cplusplus +} +#endif + +#endif /* __STM32F4xx_HAL_I2C_EX_H */ + + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c.c b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c.c new file mode 100644 index 00000000..bc52bfd9 --- /dev/null +++ b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c.c @@ -0,0 +1,5494 @@ +/** + ****************************************************************************** + * @file stm32f4xx_hal_i2c.c + * @author MCD Application Team + * @brief I2C HAL module driver. + * This file provides firmware functions to manage the following + * functionalities of the Inter Integrated Circuit (I2C) peripheral: + * + Initialization and de-initialization functions + * + IO operation functions + * + Peripheral State, Mode and Error functions + * + @verbatim + ============================================================================== + ##### How to use this driver ##### + ============================================================================== + [..] + The I2C HAL driver can be used as follows: + + (#) Declare a I2C_HandleTypeDef handle structure, for example: + I2C_HandleTypeDef hi2c; + + (#)Initialize the I2C low level resources by implementing the HAL_I2C_MspInit() API: + (##) Enable the I2Cx interface clock + (##) I2C pins configuration + (+++) Enable the clock for the I2C GPIOs + (+++) Configure I2C pins as alternate function open-drain + (##) NVIC configuration if you need to use interrupt process + (+++) Configure the I2Cx interrupt priority + (+++) Enable the NVIC I2C IRQ Channel + (##) DMA Configuration if you need to use DMA process + (+++) Declare a DMA_HandleTypeDef handle structure for the transmit or receive stream + (+++) Enable the DMAx interface clock using + (+++) Configure the DMA handle parameters + (+++) Configure the DMA Tx or Rx Stream + (+++) Associate the initialized DMA handle to the hi2c DMA Tx or Rx handle + (+++) Configure the priority and enable the NVIC for the transfer complete interrupt on + the DMA Tx or Rx Stream + + (#) Configure the Communication Speed, Duty cycle, Addressing mode, Own Address1, + Dual Addressing mode, Own Address2, General call and Nostretch mode in the hi2c Init structure. + + (#) Initialize the I2C registers by calling the HAL_I2C_Init(), configures also the low level Hardware + (GPIO, CLOCK, NVIC...etc) by calling the customized HAL_I2C_MspInit(&hi2c) API. + + (#) To check if target device is ready for communication, use the function HAL_I2C_IsDeviceReady() + + (#) For I2C IO and IO MEM operations, three operation modes are available within this driver : + + *** Polling mode IO operation *** + ================================= + [..] + (+) Transmit in master mode an amount of data in blocking mode using HAL_I2C_Master_Transmit() + (+) Receive in master mode an amount of data in blocking mode using HAL_I2C_Master_Receive() + (+) Transmit in slave mode an amount of data in blocking mode using HAL_I2C_Slave_Transmit() + (+) Receive in slave mode an amount of data in blocking mode using HAL_I2C_Slave_Receive() + + *** Polling mode IO MEM operation *** + ===================================== + [..] + (+) Write an amount of data in blocking mode to a specific memory address using HAL_I2C_Mem_Write() + (+) Read an amount of data in blocking mode from a specific memory address using HAL_I2C_Mem_Read() + + + *** Interrupt mode IO operation *** + =================================== + [..] + (+) Transmit in master mode an amount of data in non blocking mode using HAL_I2C_Master_Transmit_IT() + (+) At transmission end of transfer HAL_I2C_MasterTxCpltCallback is executed and user can + add his own code by customization of function pointer HAL_I2C_MasterTxCpltCallback + (+) Receive in master mode an amount of data in non blocking mode using HAL_I2C_Master_Receive_IT() + (+) At reception end of transfer HAL_I2C_MasterRxCpltCallback is executed and user can + add his own code by customization of function pointer HAL_I2C_MasterRxCpltCallback + (+) Transmit in slave mode an amount of data in non blocking mode using HAL_I2C_Slave_Transmit_IT() + (+) At transmission end of transfer HAL_I2C_SlaveTxCpltCallback is executed and user can + add his own code by customization of function pointer HAL_I2C_SlaveTxCpltCallback + (+) Receive in slave mode an amount of data in non blocking mode using HAL_I2C_Slave_Receive_IT() + (+) At reception end of transfer HAL_I2C_SlaveRxCpltCallback is executed and user can + add his own code by customization of function pointer HAL_I2C_SlaveRxCpltCallback + (+) In case of transfer Error, HAL_I2C_ErrorCallback() function is executed and user can + add his own code by customization of function pointer HAL_I2C_ErrorCallback + (+) Abort a master I2C process communication with Interrupt using HAL_I2C_Master_Abort_IT() + (+) End of abort process, HAL_I2C_AbortCpltCallback() is executed and user can + add his own code by customization of function pointer HAL_I2C_AbortCpltCallback() + + *** Interrupt mode IO sequential operation *** + ============================================== + [..] + (@) These interfaces allow to manage a sequential transfer with a repeated start condition + when a direction change during transfer + [..] + (+) A specific option field manage the different steps of a sequential transfer + (+) Option field values are defined through @ref I2C_XFEROPTIONS and are listed below: + (++) I2C_FIRST_AND_LAST_FRAME: No sequential usage, functionnal is same as associated interfaces in no sequential mode + (++) I2C_FIRST_FRAME: Sequential usage, this option allow to manage a sequence with start condition, address + and data to transfer without a final stop condition + (++) I2C_NEXT_FRAME: Sequential usage, this option allow to manage a sequence with a restart condition, address + and with new data to transfer if the direction change or manage only the new data to transfer + if no direction change and without a final stop condition in both cases + (++) I2C_LAST_FRAME: Sequential usage, this option allow to manage a sequance with a restart condition, address + and with new data to transfer if the direction change or manage only the new data to transfer + if no direction change and with a final stop condition in both cases + + (+) Differents sequential I2C interfaces are listed below: + (++) Sequential transmit in master I2C mode an amount of data in non-blocking mode using HAL_I2C_Master_Sequential_Transmit_IT() + (+++) At transmission end of current frame transfer, HAL_I2C_MasterTxCpltCallback() is executed and user can + add his own code by customization of function pointer HAL_I2C_MasterTxCpltCallback() + (++) Sequential receive in master I2C mode an amount of data in non-blocking mode using HAL_I2C_Master_Sequential_Receive_IT() + (+++) At reception end of current frame transfer, HAL_I2C_MasterRxCpltCallback() is executed and user can + add his own code by customization of function pointer HAL_I2C_MasterRxCpltCallback() + (++) Abort a master I2C process communication with Interrupt using HAL_I2C_Master_Abort_IT() + (+++) End of abort process, HAL_I2C_AbortCpltCallback() is executed and user can + add his own code by customization of function pointer HAL_I2C_AbortCpltCallback() + (++) Enable/disable the Address listen mode in slave I2C mode using HAL_I2C_EnableListen_IT() HAL_I2C_DisableListen_IT() + (+++) When address slave I2C match, HAL_I2C_AddrCallback() is executed and user can + add his own code to check the Address Match Code and the transmission direction request by master (Write/Read). + (+++) At Listen mode end HAL_I2C_ListenCpltCallback() is executed and user can + add his own code by customization of function pointer HAL_I2C_ListenCpltCallback() + (++) Sequential transmit in slave I2C mode an amount of data in non-blocking mode using HAL_I2C_Slave_Sequential_Transmit_IT() + (+++) At transmission end of current frame transfer, HAL_I2C_SlaveTxCpltCallback() is executed and user can + add his own code by customization of function pointer HAL_I2C_SlaveTxCpltCallback() + (++) Sequential receive in slave I2C mode an amount of data in non-blocking mode using HAL_I2C_Slave_Sequential_Receive_IT() + (+++) At reception end of current frame transfer, HAL_I2C_SlaveRxCpltCallback() is executed and user can + add his own code by customization of function pointer HAL_I2C_SlaveRxCpltCallback() + (++) In case of transfer Error, HAL_I2C_ErrorCallback() function is executed and user can + add his own code by customization of function pointer HAL_I2C_ErrorCallback() + (++) Abort a master I2C process communication with Interrupt using HAL_I2C_Master_Abort_IT() + (++) End of abort process, HAL_I2C_AbortCpltCallback() is executed and user can + add his own code by customization of function pointer HAL_I2C_AbortCpltCallback() + + *** Interrupt mode IO MEM operation *** + ======================================= + [..] + (+) Write an amount of data in no-blocking mode with Interrupt to a specific memory address using + HAL_I2C_Mem_Write_IT() + (+) At MEM end of write transfer HAL_I2C_MemTxCpltCallback is executed and user can + add his own code by customization of function pointer HAL_I2C_MemTxCpltCallback + (+) Read an amount of data in no-blocking mode with Interrupt from a specific memory address using + HAL_I2C_Mem_Read_IT() + (+) At MEM end of read transfer HAL_I2C_MemRxCpltCallback is executed and user can + add his own code by customization of function pointer HAL_I2C_MemRxCpltCallback + (+) In case of transfer Error, HAL_I2C_ErrorCallback() function is executed and user can + add his own code by customization of function pointer HAL_I2C_ErrorCallback + + *** DMA mode IO operation *** + ============================== + [..] + (+) Transmit in master mode an amount of data in non blocking mode (DMA) using + HAL_I2C_Master_Transmit_DMA() + (+) At transmission end of transfer HAL_I2C_MasterTxCpltCallback is executed and user can + add his own code by customization of function pointer HAL_I2C_MasterTxCpltCallback + (+) Receive in master mode an amount of data in non blocking mode (DMA) using + HAL_I2C_Master_Receive_DMA() + (+) At reception end of transfer HAL_I2C_MasterRxCpltCallback is executed and user can + add his own code by customization of function pointer HAL_I2C_MasterRxCpltCallback + (+) Transmit in slave mode an amount of data in non blocking mode (DMA) using + HAL_I2C_Slave_Transmit_DMA() + (+) At transmission end of transfer HAL_I2C_SlaveTxCpltCallback is executed and user can + add his own code by customization of function pointer HAL_I2C_SlaveTxCpltCallback + (+) Receive in slave mode an amount of data in non blocking mode (DMA) using + HAL_I2C_Slave_Receive_DMA() + (+) At reception end of transfer HAL_I2C_SlaveRxCpltCallback is executed and user can + add his own code by customization of function pointer HAL_I2C_SlaveRxCpltCallback + (+) In case of transfer Error, HAL_I2C_ErrorCallback() function is executed and user can + add his own code by customization of function pointer HAL_I2C_ErrorCallback + (+) Abort a master I2C process communication with Interrupt using HAL_I2C_Master_Abort_IT() + (+) End of abort process, HAL_I2C_AbortCpltCallback() is executed and user can + add his own code by customization of function pointer HAL_I2C_AbortCpltCallback() + + *** DMA mode IO MEM operation *** + ================================= + [..] + (+) Write an amount of data in no-blocking mode with DMA to a specific memory address using + HAL_I2C_Mem_Write_DMA() + (+) At MEM end of write transfer HAL_I2C_MemTxCpltCallback is executed and user can + add his own code by customization of function pointer HAL_I2C_MemTxCpltCallback + (+) Read an amount of data in no-blocking mode with DMA from a specific memory address using + HAL_I2C_Mem_Read_DMA() + (+) At MEM end of read transfer HAL_I2C_MemRxCpltCallback is executed and user can + add his own code by customization of function pointer HAL_I2C_MemRxCpltCallback + (+) In case of transfer Error, HAL_I2C_ErrorCallback() function is executed and user can + add his own code by customization of function pointer HAL_I2C_ErrorCallback + + + *** I2C HAL driver macros list *** + ================================== + [..] + Below the list of most used macros in I2C HAL driver. + + (+) __HAL_I2C_ENABLE: Enable the I2C peripheral + (+) __HAL_I2C_DISABLE: Disable the I2C peripheral + (+) __HAL_I2C_GET_FLAG : Checks whether the specified I2C flag is set or not + (+) __HAL_I2C_CLEAR_FLAG : Clear the specified I2C pending flag + (+) __HAL_I2C_ENABLE_IT: Enable the specified I2C interrupt + (+) __HAL_I2C_DISABLE_IT: Disable the specified I2C interrupt + + [..] + (@) You can refer to the I2C HAL driver header file for more useful macros + + + @endverbatim + ****************************************************************************** + * @attention + * + *

© COPYRIGHT(c) 2017 STMicroelectronics

+ * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of STMicroelectronics nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************** + */ + +/* Includes ------------------------------------------------------------------*/ +#include "stm32f4xx_hal.h" + +/** @addtogroup STM32F4xx_HAL_Driver + * @{ + */ + +/** @defgroup I2C I2C + * @brief I2C HAL module driver + * @{ + */ + +#ifdef HAL_I2C_MODULE_ENABLED + +/* Private typedef -----------------------------------------------------------*/ +/* Private define ------------------------------------------------------------*/ +/** @addtogroup I2C_Private_Define + * @{ + */ +#define I2C_TIMEOUT_FLAG 35U /*!< Timeout 35 ms */ +#define I2C_TIMEOUT_BUSY_FLAG 25U /*!< Timeout 25 ms */ +#define I2C_NO_OPTION_FRAME 0xFFFF0000U /*!< XferOptions default value */ + +/* Private define for @ref PreviousState usage */ +#define I2C_STATE_MSK ((uint32_t)((HAL_I2C_STATE_BUSY_TX | HAL_I2C_STATE_BUSY_RX) & (~(uint32_t)HAL_I2C_STATE_READY))) /*!< Mask State define, keep only RX and TX bits */ +#define I2C_STATE_NONE ((uint32_t)(HAL_I2C_MODE_NONE)) /*!< Default Value */ +#define I2C_STATE_MASTER_BUSY_TX ((uint32_t)((HAL_I2C_STATE_BUSY_TX & I2C_STATE_MSK) | HAL_I2C_MODE_MASTER)) /*!< Master Busy TX, combinaison of State LSB and Mode enum */ +#define I2C_STATE_MASTER_BUSY_RX ((uint32_t)((HAL_I2C_STATE_BUSY_RX & I2C_STATE_MSK) | HAL_I2C_MODE_MASTER)) /*!< Master Busy RX, combinaison of State LSB and Mode enum */ +#define I2C_STATE_SLAVE_BUSY_TX ((uint32_t)((HAL_I2C_STATE_BUSY_TX & I2C_STATE_MSK) | HAL_I2C_MODE_SLAVE)) /*!< Slave Busy TX, combinaison of State LSB and Mode enum */ +#define I2C_STATE_SLAVE_BUSY_RX ((uint32_t)((HAL_I2C_STATE_BUSY_RX & I2C_STATE_MSK) | HAL_I2C_MODE_SLAVE)) /*!< Slave Busy RX, combinaison of State LSB and Mode enum */ + +/** + * @} + */ + +/* Private macro -------------------------------------------------------------*/ +/* Private variables ---------------------------------------------------------*/ +/* Private function prototypes -----------------------------------------------*/ +/** @addtogroup I2C_Private_Functions + * @{ + */ +/* Private functions to handle DMA transfer */ +static void I2C_DMAXferCplt(DMA_HandleTypeDef *hdma); +static void I2C_DMAError(DMA_HandleTypeDef *hdma); +static void I2C_DMAAbort(DMA_HandleTypeDef *hdma); + +static void I2C_ITError(I2C_HandleTypeDef *hi2c); + +static HAL_StatusTypeDef I2C_MasterRequestWrite(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Timeout, uint32_t Tickstart); +static HAL_StatusTypeDef I2C_MasterRequestRead(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Timeout, uint32_t Tickstart); +static HAL_StatusTypeDef I2C_RequestMemoryWrite(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint32_t Timeout, uint32_t Tickstart); +static HAL_StatusTypeDef I2C_RequestMemoryRead(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint32_t Timeout, uint32_t Tickstart); +static HAL_StatusTypeDef I2C_WaitOnFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Flag, FlagStatus Status, uint32_t Timeout, uint32_t Tickstart); +static HAL_StatusTypeDef I2C_WaitOnMasterAddressFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Flag, uint32_t Timeout, uint32_t Tickstart); +static HAL_StatusTypeDef I2C_WaitOnTXEFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout, uint32_t Tickstart); +static HAL_StatusTypeDef I2C_WaitOnBTFFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout, uint32_t Tickstart); +static HAL_StatusTypeDef I2C_WaitOnRXNEFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout, uint32_t Tickstart); +static HAL_StatusTypeDef I2C_WaitOnSTOPFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout, uint32_t Tickstart); +static HAL_StatusTypeDef I2C_IsAcknowledgeFailed(I2C_HandleTypeDef *hi2c); + +/* Private functions for I2C transfer IRQ handler */ +static HAL_StatusTypeDef I2C_MasterTransmit_TXE(I2C_HandleTypeDef *hi2c); +static HAL_StatusTypeDef I2C_MasterTransmit_BTF(I2C_HandleTypeDef *hi2c); +static HAL_StatusTypeDef I2C_MasterReceive_RXNE(I2C_HandleTypeDef *hi2c); +static HAL_StatusTypeDef I2C_MasterReceive_BTF(I2C_HandleTypeDef *hi2c); +static HAL_StatusTypeDef I2C_Master_SB(I2C_HandleTypeDef *hi2c); +static HAL_StatusTypeDef I2C_Master_ADD10(I2C_HandleTypeDef *hi2c); +static HAL_StatusTypeDef I2C_Master_ADDR(I2C_HandleTypeDef *hi2c); + +static HAL_StatusTypeDef I2C_SlaveTransmit_TXE(I2C_HandleTypeDef *hi2c); +static HAL_StatusTypeDef I2C_SlaveTransmit_BTF(I2C_HandleTypeDef *hi2c); +static HAL_StatusTypeDef I2C_SlaveReceive_RXNE(I2C_HandleTypeDef *hi2c); +static HAL_StatusTypeDef I2C_SlaveReceive_BTF(I2C_HandleTypeDef *hi2c); +static HAL_StatusTypeDef I2C_Slave_ADDR(I2C_HandleTypeDef *hi2c); +static HAL_StatusTypeDef I2C_Slave_STOPF(I2C_HandleTypeDef *hi2c); +static HAL_StatusTypeDef I2C_Slave_AF(I2C_HandleTypeDef *hi2c); +/** + * @} + */ + +/* Exported functions --------------------------------------------------------*/ +/** @defgroup I2C_Exported_Functions I2C Exported Functions + * @{ + */ + +/** @defgroup I2C_Exported_Functions_Group1 Initialization and de-initialization functions + * @brief Initialization and Configuration functions + * +@verbatim + =============================================================================== + ##### Initialization and de-initialization functions ##### + =============================================================================== + [..] This subsection provides a set of functions allowing to initialize and + de-initialize the I2Cx peripheral: + + (+) User must Implement HAL_I2C_MspInit() function in which he configures + all related peripherals resources (CLOCK, GPIO, DMA, IT and NVIC). + + (+) Call the function HAL_I2C_Init() to configure the selected device with + the selected configuration: + (++) Communication Speed + (++) Duty cycle + (++) Addressing mode + (++) Own Address 1 + (++) Dual Addressing mode + (++) Own Address 2 + (++) General call mode + (++) Nostretch mode + + (+) Call the function HAL_I2C_DeInit() to restore the default configuration + of the selected I2Cx peripheral. + +@endverbatim + * @{ + */ + +/** + * @brief Initializes the I2C according to the specified parameters + * in the I2C_InitTypeDef and create the associated handle. + * @param hi2c pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Init(I2C_HandleTypeDef *hi2c) +{ + uint32_t freqrange = 0U; + uint32_t pclk1 = 0U; + + /* Check the I2C handle allocation */ + if(hi2c == NULL) + { + return HAL_ERROR; + } + + /* Check the parameters */ + assert_param(IS_I2C_ALL_INSTANCE(hi2c->Instance)); + assert_param(IS_I2C_CLOCK_SPEED(hi2c->Init.ClockSpeed)); + assert_param(IS_I2C_DUTY_CYCLE(hi2c->Init.DutyCycle)); + assert_param(IS_I2C_OWN_ADDRESS1(hi2c->Init.OwnAddress1)); + assert_param(IS_I2C_ADDRESSING_MODE(hi2c->Init.AddressingMode)); + assert_param(IS_I2C_DUAL_ADDRESS(hi2c->Init.DualAddressMode)); + assert_param(IS_I2C_OWN_ADDRESS2(hi2c->Init.OwnAddress2)); + assert_param(IS_I2C_GENERAL_CALL(hi2c->Init.GeneralCallMode)); + assert_param(IS_I2C_NO_STRETCH(hi2c->Init.NoStretchMode)); + + if(hi2c->State == HAL_I2C_STATE_RESET) + { + /* Allocate lock resource and initialize it */ + hi2c->Lock = HAL_UNLOCKED; + /* Init the low level hardware : GPIO, CLOCK, NVIC */ + HAL_I2C_MspInit(hi2c); + } + + hi2c->State = HAL_I2C_STATE_BUSY; + + /* Disable the selected I2C peripheral */ + __HAL_I2C_DISABLE(hi2c); + + /* Get PCLK1 frequency */ + pclk1 = HAL_RCC_GetPCLK1Freq(); + + /* Calculate frequency range */ + freqrange = I2C_FREQRANGE(pclk1); + + /*---------------------------- I2Cx CR2 Configuration ----------------------*/ + /* Configure I2Cx: Frequency range */ + hi2c->Instance->CR2 = freqrange; + + /*---------------------------- I2Cx TRISE Configuration --------------------*/ + /* Configure I2Cx: Rise Time */ + hi2c->Instance->TRISE = I2C_RISE_TIME(freqrange, hi2c->Init.ClockSpeed); + + /*---------------------------- I2Cx CCR Configuration ----------------------*/ + /* Configure I2Cx: Speed */ + hi2c->Instance->CCR = I2C_SPEED(pclk1, hi2c->Init.ClockSpeed, hi2c->Init.DutyCycle); + + /*---------------------------- I2Cx CR1 Configuration ----------------------*/ + /* Configure I2Cx: Generalcall and NoStretch mode */ + hi2c->Instance->CR1 = (hi2c->Init.GeneralCallMode | hi2c->Init.NoStretchMode); + + /*---------------------------- I2Cx OAR1 Configuration ---------------------*/ + /* Configure I2Cx: Own Address1 and addressing mode */ + hi2c->Instance->OAR1 = (hi2c->Init.AddressingMode | hi2c->Init.OwnAddress1); + + /*---------------------------- I2Cx OAR2 Configuration ---------------------*/ + /* Configure I2Cx: Dual mode and Own Address2 */ + hi2c->Instance->OAR2 = (hi2c->Init.DualAddressMode | hi2c->Init.OwnAddress2); + + /* Enable the selected I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + hi2c->State = HAL_I2C_STATE_READY; + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->Mode = HAL_I2C_MODE_NONE; + + return HAL_OK; +} + +/** + * @brief DeInitializes the I2C peripheral. + * @param hi2c pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_DeInit(I2C_HandleTypeDef *hi2c) +{ + /* Check the I2C handle allocation */ + if(hi2c == NULL) + { + return HAL_ERROR; + } + + /* Check the parameters */ + assert_param(IS_I2C_ALL_INSTANCE(hi2c->Instance)); + + hi2c->State = HAL_I2C_STATE_BUSY; + + /* Disable the I2C Peripheral Clock */ + __HAL_I2C_DISABLE(hi2c); + + /* DeInit the low level hardware: GPIO, CLOCK, NVIC */ + HAL_I2C_MspDeInit(hi2c); + + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + hi2c->State = HAL_I2C_STATE_RESET; + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->Mode = HAL_I2C_MODE_NONE; + + /* Release Lock */ + __HAL_UNLOCK(hi2c); + + return HAL_OK; +} + +/** + * @brief I2C MSP Init. + * @param hi2c pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval None + */ + __weak void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c) +{ + /* Prevent unused argument(s) compilation warning */ + UNUSED(hi2c); + /* NOTE : This function Should not be modified, when the callback is needed, + the HAL_I2C_MspInit could be implemented in the user file + */ +} + +/** + * @brief I2C MSP DeInit + * @param hi2c pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval None + */ + __weak void HAL_I2C_MspDeInit(I2C_HandleTypeDef *hi2c) +{ + /* Prevent unused argument(s) compilation warning */ + UNUSED(hi2c); + /* NOTE : This function Should not be modified, when the callback is needed, + the HAL_I2C_MspDeInit could be implemented in the user file + */ +} + +/** + * @} + */ + +/** @defgroup I2C_Exported_Functions_Group2 IO operation functions + * @brief Data transfers functions + * +@verbatim + =============================================================================== + ##### IO operation functions ##### + =============================================================================== + [..] + This subsection provides a set of functions allowing to manage the I2C data + transfers. + + (#) There are two modes of transfer: + (++) Blocking mode : The communication is performed in the polling mode. + The status of all data processing is returned by the same function + after finishing transfer. + (++) No-Blocking mode : The communication is performed using Interrupts + or DMA. These functions return the status of the transfer startup. + The end of the data processing will be indicated through the + dedicated I2C IRQ when using Interrupt mode or the DMA IRQ when + using DMA mode. + + (#) Blocking mode functions are : + (++) HAL_I2C_Master_Transmit() + (++) HAL_I2C_Master_Receive() + (++) HAL_I2C_Slave_Transmit() + (++) HAL_I2C_Slave_Receive() + (++) HAL_I2C_Mem_Write() + (++) HAL_I2C_Mem_Read() + (++) HAL_I2C_IsDeviceReady() + + (#) No-Blocking mode functions with Interrupt are : + (++) HAL_I2C_Master_Transmit_IT() + (++) HAL_I2C_Master_Receive_IT() + (++) HAL_I2C_Slave_Transmit_IT() + (++) HAL_I2C_Slave_Receive_IT() + (++) HAL_I2C_Master_Sequential_Transmit_IT() + (++) HAL_I2C_Master_Sequential_Receive_IT() + (++) HAL_I2C_Slave_Sequential_Transmit_IT() + (++) HAL_I2C_Slave_Sequential_Receive_IT() + (++) HAL_I2C_Mem_Write_IT() + (++) HAL_I2C_Mem_Read_IT() + + (#) No-Blocking mode functions with DMA are : + (++) HAL_I2C_Master_Transmit_DMA() + (++) HAL_I2C_Master_Receive_DMA() + (++) HAL_I2C_Slave_Transmit_DMA() + (++) HAL_I2C_Slave_Receive_DMA() + (++) HAL_I2C_Mem_Write_DMA() + (++) HAL_I2C_Mem_Read_DMA() + + (#) A set of Transfer Complete Callbacks are provided in non Blocking mode: + (++) HAL_I2C_MemTxCpltCallback() + (++) HAL_I2C_MemRxCpltCallback() + (++) HAL_I2C_MasterTxCpltCallback() + (++) HAL_I2C_MasterRxCpltCallback() + (++) HAL_I2C_SlaveTxCpltCallback() + (++) HAL_I2C_SlaveRxCpltCallback() + (++) HAL_I2C_ErrorCallback() + (++) HAL_I2C_AbortCpltCallback() + +@endverbatim + * @{ + */ + +/** + * @brief Transmits in master mode an amount of data in blocking mode. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param DevAddress Target device address The device 7 bits address value + * in datasheet must be shifted to the left before calling the interface + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @param Timeout Timeout duration + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout) +{ + uint32_t tickstart = 0x00U; + + /* Init tickstart for timeout management*/ + tickstart = HAL_GetTick(); + + if(hi2c->State == HAL_I2C_STATE_READY) + { + /* Wait until BUSY flag is reset */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK) + { + return HAL_BUSY; + } + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_TX; + hi2c->Mode = HAL_I2C_MODE_MASTER; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferCount = Size; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->XferSize = hi2c->XferCount; + + /* Send Slave Address */ + if(I2C_MasterRequestWrite(hi2c, DevAddress, Timeout, tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + return HAL_ERROR; + } + else + { + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + return HAL_TIMEOUT; + } + } + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + while(hi2c->XferSize > 0U) + { + /* Wait until TXE flag is set */ + if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + + /* Write data to DR */ + hi2c->Instance->DR = (*hi2c->pBuffPtr++); + hi2c->XferCount--; + hi2c->XferSize--; + + if((__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) && (hi2c->XferSize != 0U)) + { + /* Write data to DR */ + hi2c->Instance->DR = (*hi2c->pBuffPtr++); + hi2c->XferCount--; + hi2c->XferSize--; + } + + /* Wait until BTF flag is set */ + if(I2C_WaitOnBTFFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + } + + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + + hi2c->State = HAL_I2C_STATE_READY; + hi2c->Mode = HAL_I2C_MODE_NONE; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Receives in master mode an amount of data in blocking mode. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param DevAddress Target device address The device 7 bits address value + * in datasheet must be shifted to the left before calling the interface + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @param Timeout Timeout duration + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout) +{ + uint32_t tickstart = 0x00U; + + /* Init tickstart for timeout management*/ + tickstart = HAL_GetTick(); + + if(hi2c->State == HAL_I2C_STATE_READY) + { + /* Wait until BUSY flag is reset */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK) + { + return HAL_BUSY; + } + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_RX; + hi2c->Mode = HAL_I2C_MODE_MASTER; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferCount = Size; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->XferSize = hi2c->XferCount; + + /* Send Slave Address */ + if(I2C_MasterRequestRead(hi2c, DevAddress, Timeout, tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + return HAL_ERROR; + } + else + { + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + return HAL_TIMEOUT; + } + } + + if(hi2c->XferSize == 0U) + { + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + } + else if(hi2c->XferSize == 1U) + { + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + } + else if(hi2c->XferSize == 2U) + { + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + /* Enable Pos */ + hi2c->Instance->CR1 |= I2C_CR1_POS; + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + } + else + { + /* Enable Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + } + + while(hi2c->XferSize > 0U) + { + if(hi2c->XferSize <= 3U) + { + /* One byte */ + if(hi2c->XferSize == 1U) + { + /* Wait until RXNE flag is set */ + if(I2C_WaitOnRXNEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_TIMEOUT) + { + return HAL_TIMEOUT; + } + else + { + return HAL_ERROR; + } + } + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferSize--; + hi2c->XferCount--; + } + /* Two bytes */ + else if(hi2c->XferSize == 2U) + { + /* Wait until BTF flag is set */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BTF, RESET, Timeout, tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferSize--; + hi2c->XferCount--; + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferSize--; + hi2c->XferCount--; + } + /* 3 Last bytes */ + else + { + /* Wait until BTF flag is set */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BTF, RESET, Timeout, tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferSize--; + hi2c->XferCount--; + + /* Wait until BTF flag is set */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BTF, RESET, Timeout, tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferSize--; + hi2c->XferCount--; + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferSize--; + hi2c->XferCount--; + } + } + else + { + /* Wait until RXNE flag is set */ + if(I2C_WaitOnRXNEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_TIMEOUT) + { + return HAL_TIMEOUT; + } + else + { + return HAL_ERROR; + } + } + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferSize--; + hi2c->XferCount--; + + if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) + { + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferSize--; + hi2c->XferCount--; + } + } + } + + hi2c->State = HAL_I2C_STATE_READY; + hi2c->Mode = HAL_I2C_MODE_NONE; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Transmits in slave mode an amount of data in blocking mode. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @param Timeout Timeout duration + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Slave_Transmit(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout) +{ + uint32_t tickstart = 0x00U; + + /* Init tickstart for timeout management*/ + tickstart = HAL_GetTick(); + + if(hi2c->State == HAL_I2C_STATE_READY) + { + if((pData == NULL) || (Size == 0U)) + { + return HAL_ERROR; + } + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_TX; + hi2c->Mode = HAL_I2C_MODE_SLAVE; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferCount = Size; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->XferSize = hi2c->XferCount; + + /* Enable Address Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + /* Wait until ADDR flag is set */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, RESET, Timeout, tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + /* If 10bit addressing mode is selected */ + if(hi2c->Init.AddressingMode == I2C_ADDRESSINGMODE_10BIT) + { + /* Wait until ADDR flag is set */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, RESET, Timeout, tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + } + + while(hi2c->XferSize > 0U) + { + /* Wait until TXE flag is set */ + if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) + { + /* Disable Address Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + + /* Write data to DR */ + hi2c->Instance->DR = (*hi2c->pBuffPtr++); + hi2c->XferCount--; + hi2c->XferSize--; + + if((__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) && (hi2c->XferSize != 0U)) + { + /* Write data to DR */ + hi2c->Instance->DR = (*hi2c->pBuffPtr++); + hi2c->XferCount--; + hi2c->XferSize--; + } + } + + /* Wait until AF flag is set */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_AF, RESET, Timeout, tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + + /* Clear AF flag */ + __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF); + + /* Disable Address Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + hi2c->State = HAL_I2C_STATE_READY; + hi2c->Mode = HAL_I2C_MODE_NONE; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Receive in slave mode an amount of data in blocking mode + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @param Timeout Timeout duration + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Slave_Receive(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout) +{ + uint32_t tickstart = 0x00U; + + /* Init tickstart for timeout management*/ + tickstart = HAL_GetTick(); + + if(hi2c->State == HAL_I2C_STATE_READY) + { + if((pData == NULL) || (Size == 0)) + { + return HAL_ERROR; + } + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_RX; + hi2c->Mode = HAL_I2C_MODE_SLAVE; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferCount = Size; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->XferSize = hi2c->XferCount; + + /* Enable Address Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + /* Wait until ADDR flag is set */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, RESET, Timeout, tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + while(hi2c->XferSize > 0U) + { + /* Wait until RXNE flag is set */ + if(I2C_WaitOnRXNEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) + { + /* Disable Address Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + if(hi2c->ErrorCode == HAL_I2C_ERROR_TIMEOUT) + { + return HAL_TIMEOUT; + } + else + { + return HAL_ERROR; + } + } + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferSize--; + hi2c->XferCount--; + + if((__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) && (Size != 0U)) + { + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferSize--; + hi2c->XferCount--; + } + } + + /* Wait until STOP flag is set */ + if(I2C_WaitOnSTOPFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) + { + /* Disable Address Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + + /* Clear STOP flag */ + __HAL_I2C_CLEAR_STOPFLAG(hi2c); + + /* Disable Address Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + hi2c->State = HAL_I2C_STATE_READY; + hi2c->Mode = HAL_I2C_MODE_NONE; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Transmit in master mode an amount of data in non-blocking mode with Interrupt + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param DevAddress Target device address The device 7 bits address value + * in datasheet must be shifted to the left before calling the interface + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size) +{ + __IO uint32_t count = 0U; + + if(hi2c->State == HAL_I2C_STATE_READY) + { + /* Wait until BUSY flag is reset */ + count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); + do + { + if(count-- == 0U) + { + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_TX; + hi2c->Mode = HAL_I2C_MODE_MASTER; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferCount = Size; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->XferSize = hi2c->XferCount; + hi2c->Devaddress = DevAddress; + + /* Generate Start */ + hi2c->Instance->CR1 |= I2C_CR1_START; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + /* Note : The I2C interrupts must be enabled after unlocking current process + to avoid the risk of I2C interrupt handle execution before current + process unlock */ + /* Enable EVT, BUF and ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Receive in master mode an amount of data in non-blocking mode with Interrupt + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param DevAddress Target device address The device 7 bits address value + * in datasheet must be shifted to the left before calling the interface + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size) +{ + __IO uint32_t count = 0U; + + if(hi2c->State == HAL_I2C_STATE_READY) + { + /* Wait until BUSY flag is reset */ + count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); + do + { + if(count-- == 0U) + { + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_RX; + hi2c->Mode = HAL_I2C_MODE_MASTER; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferCount = Size; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->XferSize = hi2c->XferCount; + hi2c->Devaddress = DevAddress; + + /* Enable Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + /* Generate Start */ + hi2c->Instance->CR1 |= I2C_CR1_START; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + /* Note : The I2C interrupts must be enabled after unlocking current process + to avoid the risk of I2C interrupt handle execution before current + process unlock */ + + /* Enable EVT, BUF and ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Sequential transmit in master mode an amount of data in non-blocking mode with Interrupt + * @note This interface allow to manage repeated start condition when a direction change during transfer + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param DevAddress Target device address The device 7 bits address value + * in datasheet must be shifted to the left before calling the interface + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @param XferOptions Options of Transfer, value of @ref I2C_XferOptions_definition + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Master_Sequential_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions) +{ + __IO uint32_t Prev_State = 0x00U; + __IO uint32_t count = 0x00U; + + /* Check the parameters */ + assert_param(IS_I2C_TRANSFER_OPTIONS_REQUEST(XferOptions)); + + if(hi2c->State == HAL_I2C_STATE_READY) + { + /* Check Busy Flag only if FIRST call of Master interface */ + if((XferOptions == I2C_FIRST_AND_LAST_FRAME) || (XferOptions == I2C_FIRST_FRAME)) + { + /* Wait until BUSY flag is reset */ + count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); + do + { + if(count-- == 0U) + { + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); + } + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_TX; + hi2c->Mode = HAL_I2C_MODE_MASTER; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferCount = Size; + hi2c->XferOptions = XferOptions; + hi2c->XferSize = hi2c->XferCount; + hi2c->Devaddress = DevAddress; + + Prev_State = hi2c->PreviousState; + + /* Generate Start */ + if((Prev_State == I2C_STATE_MASTER_BUSY_RX) || (Prev_State == I2C_STATE_NONE)) + { + /* Generate Start condition if first transfer */ + if((XferOptions == I2C_FIRST_AND_LAST_FRAME) || (XferOptions == I2C_FIRST_FRAME)) + { + /* Generate Start */ + hi2c->Instance->CR1 |= I2C_CR1_START; + } + else + { + /* Generate ReStart */ + hi2c->Instance->CR1 |= I2C_CR1_START; + } + } + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + /* Note : The I2C interrupts must be enabled after unlocking current process + to avoid the risk of I2C interrupt handle execution before current + process unlock */ + + /* Enable EVT, BUF and ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Sequential receive in master mode an amount of data in non-blocking mode with Interrupt + * @note This interface allow to manage repeated start condition when a direction change during transfer + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param DevAddress Target device address The device 7 bits address value + * in datasheet must be shifted to the left before calling the interface + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @param XferOptions Options of Transfer, value of @ref I2C_XferOptions_definition + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Master_Sequential_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions) +{ + __IO uint32_t count = 0U; + + /* Check the parameters */ + assert_param(IS_I2C_TRANSFER_OPTIONS_REQUEST(XferOptions)); + + if(hi2c->State == HAL_I2C_STATE_READY) + { + /* Check Busy Flag only if FIRST call of Master interface */ + if((XferOptions == I2C_FIRST_AND_LAST_FRAME) || (XferOptions == I2C_FIRST_FRAME)) + { + /* Wait until BUSY flag is reset */ + count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); + do + { + if(count-- == 0U) + { + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); + } + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_RX; + hi2c->Mode = HAL_I2C_MODE_MASTER; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferCount = Size; + hi2c->XferOptions = XferOptions; + hi2c->XferSize = hi2c->XferCount; + hi2c->Devaddress = DevAddress; + + if((hi2c->PreviousState == I2C_STATE_MASTER_BUSY_TX) || (hi2c->PreviousState == I2C_STATE_NONE)) + { + /* Generate Start condition if first transfer */ + if((XferOptions == I2C_FIRST_AND_LAST_FRAME) || (XferOptions == I2C_FIRST_FRAME) || (XferOptions == I2C_NO_OPTION_FRAME)) + { + /* Enable Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + /* Generate Start */ + hi2c->Instance->CR1 |= I2C_CR1_START; + } + else if(hi2c->PreviousState == I2C_STATE_MASTER_BUSY_TX) + { + /* Enable Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + /* Generate ReStart */ + hi2c->Instance->CR1 |= I2C_CR1_START; + } + } + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + /* Note : The I2C interrupts must be enabled after unlocking current process + to avoid the risk of I2C interrupt handle execution before current + process unlock */ + + /* Enable EVT, BUF and ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Transmit in slave mode an amount of data in non-blocking mode with Interrupt + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Slave_Transmit_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size) +{ + __IO uint32_t count = 0U; + + if(hi2c->State == HAL_I2C_STATE_READY) + { + if((pData == NULL) || (Size == 0U)) + { + return HAL_ERROR; + } + + /* Wait until BUSY flag is reset */ + count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); + do + { + if(count-- == 0U) + { + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_TX; + hi2c->Mode = HAL_I2C_MODE_SLAVE; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferCount = Size; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->XferSize = hi2c->XferCount; + + /* Enable Address Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + /* Note : The I2C interrupts must be enabled after unlocking current process + to avoid the risk of I2C interrupt handle execution before current + process unlock */ + + /* Enable EVT, BUF and ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Receive in slave mode an amount of data in non-blocking mode with Interrupt + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Slave_Receive_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size) +{ + __IO uint32_t count = 0U; + + if(hi2c->State == HAL_I2C_STATE_READY) + { + if((pData == NULL) || (Size == 0U)) + { + return HAL_ERROR; + } + + /* Wait until BUSY flag is reset */ + count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); + do + { + if(count-- == 0U) + { + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_RX; + hi2c->Mode = HAL_I2C_MODE_SLAVE; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferSize = Size; + hi2c->XferCount = Size; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + + /* Enable Address Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + /* Note : The I2C interrupts must be enabled after unlocking current process + to avoid the risk of I2C interrupt handle execution before current + process unlock */ + + /* Enable EVT, BUF and ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Sequential transmit in slave mode an amount of data in no-blocking mode with Interrupt + * @note This interface allow to manage repeated start condition when a direction change during transfer + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @param XferOptions Options of Transfer, value of @ref I2C_XferOptions_definition + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Slave_Sequential_Transmit_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t XferOptions) +{ + /* Check the parameters */ + assert_param(IS_I2C_TRANSFER_OPTIONS_REQUEST(XferOptions)); + + if(hi2c->State == HAL_I2C_STATE_LISTEN) + { + if((pData == NULL) || (Size == 0U)) + { + return HAL_ERROR; + } + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_TX_LISTEN; + hi2c->Mode = HAL_I2C_MODE_SLAVE; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferCount = Size; + hi2c->XferOptions = XferOptions; + hi2c->XferSize = hi2c->XferCount; + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + /* Note : The I2C interrupts must be enabled after unlocking current process + to avoid the risk of I2C interrupt handle execution before current + process unlock */ + + /* Enable EVT, BUF and ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Sequential receive in slave mode an amount of data in non-blocking mode with Interrupt + * @note This interface allow to manage repeated start condition when a direction change during transfer + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @param XferOptions Options of Transfer, value of @ref I2C_XferOptions_definition + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Slave_Sequential_Receive_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t XferOptions) +{ + /* Check the parameters */ + assert_param(IS_I2C_TRANSFER_OPTIONS_REQUEST(XferOptions)); + + if(hi2c->State == HAL_I2C_STATE_LISTEN) + { + if((pData == NULL) || (Size == 0U)) + { + return HAL_ERROR; + } + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_RX_LISTEN; + hi2c->Mode = HAL_I2C_MODE_SLAVE; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferCount = Size; + hi2c->XferOptions = XferOptions; + hi2c->XferSize = hi2c->XferCount; + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + /* Note : The I2C interrupts must be enabled after unlocking current process + to avoid the risk of I2C interrupt handle execution before current + process unlock */ + + /* Enable EVT, BUF and ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Enable the Address listen mode with Interrupt. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_EnableListen_IT(I2C_HandleTypeDef *hi2c) +{ + if(hi2c->State == HAL_I2C_STATE_READY) + { + hi2c->State = HAL_I2C_STATE_LISTEN; + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Enable Address Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + /* Enable EVT and ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_ERR); + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Disable the Address listen mode with Interrupt. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_DisableListen_IT(I2C_HandleTypeDef *hi2c) +{ + /* Declaration of tmp to prevent undefined behavior of volatile usage */ + uint32_t tmp; + + /* Disable Address listen mode only if a transfer is not ongoing */ + if(hi2c->State == HAL_I2C_STATE_LISTEN) + { + tmp = (uint32_t)(hi2c->State) & I2C_STATE_MSK; + hi2c->PreviousState = tmp | (uint32_t)(hi2c->Mode); + hi2c->State = HAL_I2C_STATE_READY; + hi2c->Mode = HAL_I2C_MODE_NONE; + + /* Disable Address Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + /* Disable EVT and ERR interrupt */ + __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_ERR); + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Transmit in master mode an amount of data in non-blocking mode with DMA + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param DevAddress Target device address The device 7 bits address value + * in datasheet must be shifted to the left before calling the interface + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size) +{ + __IO uint32_t count = 0U; + + if(hi2c->State == HAL_I2C_STATE_READY) + { + /* Wait until BUSY flag is reset */ + count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); + do + { + if(count-- == 0U) + { + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_TX; + hi2c->Mode = HAL_I2C_MODE_MASTER; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferCount = Size; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->XferSize = hi2c->XferCount; + hi2c->Devaddress = DevAddress; + + if(hi2c->XferSize > 0U) + { + /* Set the I2C DMA transfer complete callback */ + hi2c->hdmatx->XferCpltCallback = I2C_DMAXferCplt; + + /* Set the DMA error callback */ + hi2c->hdmatx->XferErrorCallback = I2C_DMAError; + + /* Set the unused DMA callbacks to NULL */ + hi2c->hdmatx->XferHalfCpltCallback = NULL; + hi2c->hdmatx->XferM1CpltCallback = NULL; + hi2c->hdmatx->XferM1HalfCpltCallback = NULL; + hi2c->hdmatx->XferAbortCallback = NULL; + + /* Enable the DMA Stream */ + HAL_DMA_Start_IT(hi2c->hdmatx, (uint32_t)hi2c->pBuffPtr, (uint32_t)&hi2c->Instance->DR, hi2c->XferSize); + + /* Enable Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + /* Generate Start */ + hi2c->Instance->CR1 |= I2C_CR1_START; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + /* Note : The I2C interrupts must be enabled after unlocking current process + to avoid the risk of I2C interrupt handle execution before current + process unlock */ + + /* Enable EVT and ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_ERR); + + /* Enable DMA Request */ + hi2c->Instance->CR2 |= I2C_CR2_DMAEN; + } + else + { + /* Enable Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + /* Generate Start */ + hi2c->Instance->CR1 |= I2C_CR1_START; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + /* Note : The I2C interrupts must be enabled after unlocking current process + to avoid the risk of I2C interrupt handle execution before current + process unlock */ + + /* Enable EVT, BUF and ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + } + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Receive in master mode an amount of data in non-blocking mode with DMA + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param DevAddress Target device address The device 7 bits address value + * in datasheet must be shifted to the left before calling the interface + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size) +{ + __IO uint32_t count = 0U; + + if(hi2c->State == HAL_I2C_STATE_READY) + { + /* Wait until BUSY flag is reset */ + count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); + do + { + if(count-- == 0U) + { + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_RX; + hi2c->Mode = HAL_I2C_MODE_MASTER; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferCount = Size; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->XferSize = hi2c->XferCount; + hi2c->Devaddress = DevAddress; + + if(hi2c->XferSize > 0U) + { + /* Set the I2C DMA transfer complete callback */ + hi2c->hdmarx->XferCpltCallback = I2C_DMAXferCplt; + + /* Set the DMA error callback */ + hi2c->hdmarx->XferErrorCallback = I2C_DMAError; + + /* Set the unused DMA callbacks to NULL */ + hi2c->hdmarx->XferHalfCpltCallback = NULL; + hi2c->hdmarx->XferM1CpltCallback = NULL; + hi2c->hdmarx->XferM1HalfCpltCallback = NULL; + hi2c->hdmarx->XferAbortCallback = NULL; + + /* Enable the DMA Stream */ + HAL_DMA_Start_IT(hi2c->hdmarx, (uint32_t)&hi2c->Instance->DR, (uint32_t)hi2c->pBuffPtr, hi2c->XferSize); + + /* Enable Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + /* Generate Start */ + hi2c->Instance->CR1 |= I2C_CR1_START; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + /* Note : The I2C interrupts must be enabled after unlocking current process + to avoid the risk of I2C interrupt handle execution before current + process unlock */ + + /* Enable EVT and ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_ERR); + + /* Enable DMA Request */ + hi2c->Instance->CR2 |= I2C_CR2_DMAEN; + } + else + { + /* Enable Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + /* Generate Start */ + hi2c->Instance->CR1 |= I2C_CR1_START; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + /* Note : The I2C interrupts must be enabled after unlocking current process + to avoid the risk of I2C interrupt handle execution before current + process unlock */ + + /* Enable EVT, BUF and ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + } + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Abort a master I2C process communication with Interrupt. + * @note This abort can be called only if state is ready + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param DevAddress Target device address The device 7 bits address value + * in datasheet must be shifted to the left before calling the interface + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Master_Abort_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress) +{ + /* Prevent unused argument(s) compilation warning */ + UNUSED(DevAddress); + + /* Abort Master transfer during Receive or Transmit process */ + if(hi2c->Mode == HAL_I2C_MODE_MASTER) + { + /* Process Locked */ + __HAL_LOCK(hi2c); + + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State = HAL_I2C_STATE_ABORT; + + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + + hi2c->XferCount = 0U; + + /* Disable EVT, BUF and ERR interrupt */ + __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + /* Call the corresponding callback to inform upper layer of End of Transfer */ + I2C_ITError(hi2c); + + return HAL_OK; + } + else + { + /* Wrong usage of abort function */ + /* This function should be used only in case of abort monitored by master device */ + return HAL_ERROR; + } +} + +/** + * @brief Transmit in slave mode an amount of data in non-blocking mode with DMA + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Slave_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size) +{ + __IO uint32_t count = 0U; + + if(hi2c->State == HAL_I2C_STATE_READY) + { + if((pData == NULL) || (Size == 0U)) + { + return HAL_ERROR; + } + + /* Wait until BUSY flag is reset */ + count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); + do + { + if(count-- == 0U) + { + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_TX; + hi2c->Mode = HAL_I2C_MODE_SLAVE; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferCount = Size; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->XferSize = hi2c->XferCount; + + /* Set the I2C DMA transfer complete callback */ + hi2c->hdmatx->XferCpltCallback = I2C_DMAXferCplt; + + /* Set the DMA error callback */ + hi2c->hdmatx->XferErrorCallback = I2C_DMAError; + + /* Set the unused DMA callbacks to NULL */ + hi2c->hdmatx->XferHalfCpltCallback = NULL; + hi2c->hdmatx->XferM1CpltCallback = NULL; + hi2c->hdmatx->XferM1HalfCpltCallback = NULL; + hi2c->hdmatx->XferAbortCallback = NULL; + + /* Enable the DMA Stream */ + HAL_DMA_Start_IT(hi2c->hdmatx, (uint32_t)hi2c->pBuffPtr, (uint32_t)&hi2c->Instance->DR, hi2c->XferSize); + + /* Enable Address Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + /* Note : The I2C interrupts must be enabled after unlocking current process + to avoid the risk of I2C interrupt handle execution before current + process unlock */ + /* Enable EVT and ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_ERR); + + /* Enable DMA Request */ + hi2c->Instance->CR2 |= I2C_CR2_DMAEN; + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Receive in slave mode an amount of data in non-blocking mode with DMA + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Slave_Receive_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size) +{ + __IO uint32_t count = 0U; + + if(hi2c->State == HAL_I2C_STATE_READY) + { + if((pData == NULL) || (Size == 0U)) + { + return HAL_ERROR; + } + + /* Wait until BUSY flag is reset */ + count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); + do + { + if(count-- == 0U) + { + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_RX; + hi2c->Mode = HAL_I2C_MODE_SLAVE; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferCount = Size; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->XferSize = hi2c->XferCount; + + /* Set the I2C DMA transfer complete callback */ + hi2c->hdmarx->XferCpltCallback = I2C_DMAXferCplt; + + /* Set the DMA error callback */ + hi2c->hdmarx->XferErrorCallback = I2C_DMAError; + + /* Set the unused DMA callbacks to NULL */ + hi2c->hdmarx->XferHalfCpltCallback = NULL; + hi2c->hdmarx->XferM1CpltCallback = NULL; + hi2c->hdmarx->XferM1HalfCpltCallback = NULL; + hi2c->hdmarx->XferAbortCallback = NULL; + + /* Enable the DMA Stream */ + HAL_DMA_Start_IT(hi2c->hdmarx, (uint32_t)&hi2c->Instance->DR, (uint32_t)hi2c->pBuffPtr, hi2c->XferSize); + + /* Enable Address Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + /* Note : The I2C interrupts must be enabled after unlocking current process + to avoid the risk of I2C interrupt handle execution before current + process unlock */ + /* Enable EVT and ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_ERR); + + /* Enable DMA Request */ + hi2c->Instance->CR2 |= I2C_CR2_DMAEN; + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} +/** + * @brief Write an amount of data in blocking mode to a specific memory address + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param DevAddress Target device address + * @param MemAddress Internal memory address + * @param MemAddSize Size of internal memory address + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @param Timeout Timeout duration + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout) +{ + uint32_t tickstart = 0x00U; + + /* Init tickstart for timeout management*/ + tickstart = HAL_GetTick(); + + /* Check the parameters */ + assert_param(IS_I2C_MEMADD_SIZE(MemAddSize)); + + if(hi2c->State == HAL_I2C_STATE_READY) + { + /* Wait until BUSY flag is reset */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK) + { + return HAL_BUSY; + } + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_TX; + hi2c->Mode = HAL_I2C_MODE_MEM; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferCount = Size; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->XferSize = hi2c->XferCount; + + /* Send Slave Address and Memory Address */ + if(I2C_RequestMemoryWrite(hi2c, DevAddress, MemAddress, MemAddSize, Timeout, tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + return HAL_ERROR; + } + else + { + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + return HAL_TIMEOUT; + } + } + + while(hi2c->XferSize > 0U) + { + /* Wait until TXE flag is set */ + if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + + /* Write data to DR */ + hi2c->Instance->DR = (*hi2c->pBuffPtr++); + hi2c->XferSize--; + hi2c->XferCount--; + + if((__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) && (hi2c->XferSize != 0U)) + { + /* Write data to DR */ + hi2c->Instance->DR = (*hi2c->pBuffPtr++); + hi2c->XferSize--; + hi2c->XferCount--; + } + } + + /* Wait until BTF flag is set */ + if(I2C_WaitOnBTFFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + + hi2c->State = HAL_I2C_STATE_READY; + hi2c->Mode = HAL_I2C_MODE_NONE; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Read an amount of data in blocking mode from a specific memory address + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param DevAddress Target device address + * @param MemAddress Internal memory address + * @param MemAddSize Size of internal memory address + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @param Timeout Timeout duration + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout) +{ + uint32_t tickstart = 0x00U; + + /* Init tickstart for timeout management*/ + tickstart = HAL_GetTick(); + + /* Check the parameters */ + assert_param(IS_I2C_MEMADD_SIZE(MemAddSize)); + + if(hi2c->State == HAL_I2C_STATE_READY) + { + /* Wait until BUSY flag is reset */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK) + { + return HAL_BUSY; + } + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_RX; + hi2c->Mode = HAL_I2C_MODE_MEM; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferCount = Size; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->XferSize = hi2c->XferCount; + + /* Send Slave Address and Memory Address */ + if(I2C_RequestMemoryRead(hi2c, DevAddress, MemAddress, MemAddSize, Timeout, tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + return HAL_ERROR; + } + else + { + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + return HAL_TIMEOUT; + } + } + + if(hi2c->XferSize == 0U) + { + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + } + else if(hi2c->XferSize == 1U) + { + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + } + else if(hi2c->XferSize == 2U) + { + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + /* Enable Pos */ + hi2c->Instance->CR1 |= I2C_CR1_POS; + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + } + else + { + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + } + + while(hi2c->XferSize > 0U) + { + if(hi2c->XferSize <= 3U) + { + /* One byte */ + if(hi2c->XferSize== 1U) + { + /* Wait until RXNE flag is set */ + if(I2C_WaitOnRXNEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_TIMEOUT) + { + return HAL_TIMEOUT; + } + else + { + return HAL_ERROR; + } + } + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferSize--; + hi2c->XferCount--; + } + /* Two bytes */ + else if(hi2c->XferSize == 2U) + { + /* Wait until BTF flag is set */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BTF, RESET, Timeout, tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferSize--; + hi2c->XferCount--; + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferSize--; + hi2c->XferCount--; + } + /* 3 Last bytes */ + else + { + /* Wait until BTF flag is set */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BTF, RESET, Timeout, tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferSize--; + hi2c->XferCount--; + + /* Wait until BTF flag is set */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BTF, RESET, Timeout, tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferSize--; + hi2c->XferCount--; + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferSize--; + hi2c->XferCount--; + } + } + else + { + /* Wait until RXNE flag is set */ + if(I2C_WaitOnRXNEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_TIMEOUT) + { + return HAL_TIMEOUT; + } + else + { + return HAL_ERROR; + } + } + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferSize--; + hi2c->XferCount--; + + if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) + { + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferSize--; + hi2c->XferCount--; + } + } + } + + hi2c->State = HAL_I2C_STATE_READY; + hi2c->Mode = HAL_I2C_MODE_NONE; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Write an amount of data in non-blocking mode with Interrupt to a specific memory address + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param DevAddress Target device address + * @param MemAddress Internal memory address + * @param MemAddSize Size of internal memory address + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Mem_Write_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size) +{ + __IO uint32_t count = 0U; + + /* Check the parameters */ + assert_param(IS_I2C_MEMADD_SIZE(MemAddSize)); + + if(hi2c->State == HAL_I2C_STATE_READY) + { + /* Wait until BUSY flag is reset */ + count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); + do + { + if(count-- == 0U) + { + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_TX; + hi2c->Mode = HAL_I2C_MODE_MEM; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferSize = Size; + hi2c->XferCount = Size; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->Devaddress = DevAddress; + hi2c->Memaddress = MemAddress; + hi2c->MemaddSize = MemAddSize; + hi2c->EventCount = 0U; + + /* Generate Start */ + hi2c->Instance->CR1 |= I2C_CR1_START; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + /* Note : The I2C interrupts must be enabled after unlocking current process + to avoid the risk of I2C interrupt handle execution before current + process unlock */ + + /* Enable EVT, BUF and ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Read an amount of data in non-blocking mode with Interrupt from a specific memory address + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param DevAddress Target device address + * @param MemAddress Internal memory address + * @param MemAddSize Size of internal memory address + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Mem_Read_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size) +{ + __IO uint32_t count = 0U; + + /* Check the parameters */ + assert_param(IS_I2C_MEMADD_SIZE(MemAddSize)); + + if(hi2c->State == HAL_I2C_STATE_READY) + { + /* Wait until BUSY flag is reset */ + count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); + do + { + if(count-- == 0U) + { + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_RX; + hi2c->Mode = HAL_I2C_MODE_MEM; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferSize = Size; + hi2c->XferCount = Size; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->Devaddress = DevAddress; + hi2c->Memaddress = MemAddress; + hi2c->MemaddSize = MemAddSize; + hi2c->EventCount = 0U; + + /* Enable Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + /* Generate Start */ + hi2c->Instance->CR1 |= I2C_CR1_START; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + if(hi2c->XferSize > 0U) + { + /* Note : The I2C interrupts must be enabled after unlocking current process + to avoid the risk of I2C interrupt handle execution before current + process unlock */ + + /* Enable EVT, BUF and ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + } + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Write an amount of data in non-blocking mode with DMA to a specific memory address + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param DevAddress Target device address + * @param MemAddress Internal memory address + * @param MemAddSize Size of internal memory address + * @param pData Pointer to data buffer + * @param Size Amount of data to be sent + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Mem_Write_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size) +{ + __IO uint32_t count = 0U; + + uint32_t tickstart = 0x00U; + + /* Init tickstart for timeout management*/ + tickstart = HAL_GetTick(); + + /* Check the parameters */ + assert_param(IS_I2C_MEMADD_SIZE(MemAddSize)); + + if(hi2c->State == HAL_I2C_STATE_READY) + { + /* Wait until BUSY flag is reset */ + count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); + do + { + if(count-- == 0U) + { + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_TX; + hi2c->Mode = HAL_I2C_MODE_MEM; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferSize = Size; + hi2c->XferCount = Size; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + + if(hi2c->XferSize > 0U) + { + /* Set the I2C DMA transfer complete callback */ + hi2c->hdmatx->XferCpltCallback = I2C_DMAXferCplt; + + /* Set the DMA error callback */ + hi2c->hdmatx->XferErrorCallback = I2C_DMAError; + + /* Set the unused DMA callbacks to NULL */ + hi2c->hdmatx->XferHalfCpltCallback = NULL; + hi2c->hdmatx->XferM1CpltCallback = NULL; + hi2c->hdmatx->XferM1HalfCpltCallback = NULL; + hi2c->hdmatx->XferAbortCallback = NULL; + + /* Enable the DMA Stream */ + HAL_DMA_Start_IT(hi2c->hdmatx, (uint32_t)hi2c->pBuffPtr, (uint32_t)&hi2c->Instance->DR, hi2c->XferSize); + + /* Send Slave Address and Memory Address */ + if(I2C_RequestMemoryWrite(hi2c, DevAddress, MemAddress, MemAddSize, I2C_TIMEOUT_FLAG, tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + return HAL_ERROR; + } + else + { + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + return HAL_TIMEOUT; + } + } + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + /* Note : The I2C interrupts must be enabled after unlocking current process + to avoid the risk of I2C interrupt handle execution before current + process unlock */ + /* Enable ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_ERR); + + /* Enable DMA Request */ + hi2c->Instance->CR2 |= I2C_CR2_DMAEN; + } + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Reads an amount of data in non-blocking mode with DMA from a specific memory address. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param DevAddress Target device address + * @param MemAddress Internal memory address + * @param MemAddSize Size of internal memory address + * @param pData Pointer to data buffer + * @param Size Amount of data to be read + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_Mem_Read_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size) +{ + uint32_t tickstart = 0x00U; + __IO uint32_t count = 0U; + + /* Init tickstart for timeout management*/ + tickstart = HAL_GetTick(); + + /* Check the parameters */ + assert_param(IS_I2C_MEMADD_SIZE(MemAddSize)); + + if(hi2c->State == HAL_I2C_STATE_READY) + { + /* Wait until BUSY flag is reset */ + count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); + do + { + if(count-- == 0U) + { + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY_RX; + hi2c->Mode = HAL_I2C_MODE_MEM; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Prepare transfer parameters */ + hi2c->pBuffPtr = pData; + hi2c->XferCount = Size; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->XferSize = hi2c->XferCount; + + if(hi2c->XferSize > 0U) + { + /* Set the I2C DMA transfer complete callback */ + hi2c->hdmarx->XferCpltCallback = I2C_DMAXferCplt; + + /* Set the DMA error callback */ + hi2c->hdmarx->XferErrorCallback = I2C_DMAError; + + /* Set the unused DMA callbacks to NULL */ + hi2c->hdmarx->XferHalfCpltCallback = NULL; + hi2c->hdmarx->XferM1CpltCallback = NULL; + hi2c->hdmarx->XferM1HalfCpltCallback = NULL; + hi2c->hdmarx->XferAbortCallback = NULL; + + /* Enable the DMA Stream */ + HAL_DMA_Start_IT(hi2c->hdmarx, (uint32_t)&hi2c->Instance->DR, (uint32_t)hi2c->pBuffPtr, hi2c->XferSize); + + /* Send Slave Address and Memory Address */ + if(I2C_RequestMemoryRead(hi2c, DevAddress, MemAddress, MemAddSize, I2C_TIMEOUT_FLAG, tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + return HAL_ERROR; + } + else + { + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + return HAL_TIMEOUT; + } + } + + if(Size == 1U) + { + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + } + else + { + /* Enable Last DMA bit */ + hi2c->Instance->CR2 |= I2C_CR2_LAST; + } + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + /* Note : The I2C interrupts must be enabled after unlocking current process + to avoid the risk of I2C interrupt handle execution before current + process unlock */ + /* Enable ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_ERR); + + /* Enable DMA Request */ + hi2c->Instance->CR2 |= I2C_CR2_DMAEN; + } + else + { + /* Send Slave Address and Memory Address */ + if(I2C_RequestMemoryRead(hi2c, DevAddress, MemAddress, MemAddSize, I2C_TIMEOUT_FLAG, tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + return HAL_ERROR; + } + else + { + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + return HAL_TIMEOUT; + } + } + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + + hi2c->State = HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + } + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Checks if target device is ready for communication. + * @note This function is used with Memory devices + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param DevAddress Target device address + * @param Trials Number of trials + * @param Timeout Timeout duration + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout) +{ + uint32_t tickstart = 0U, tmp1 = 0U, tmp2 = 0U, tmp3 = 0U, I2C_Trials = 1U; + + /* Get tick */ + tickstart = HAL_GetTick(); + + if(hi2c->State == HAL_I2C_STATE_READY) + { + /* Wait until BUSY flag is reset */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK) + { + return HAL_BUSY; + } + + /* Process Locked */ + __HAL_LOCK(hi2c); + + /* Check if the I2C is already enabled */ + if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) + { + /* Enable I2C peripheral */ + __HAL_I2C_ENABLE(hi2c); + } + + /* Disable Pos */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + hi2c->State = HAL_I2C_STATE_BUSY; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + + do + { + /* Generate Start */ + hi2c->Instance->CR1 |= I2C_CR1_START; + + /* Wait until SB flag is set */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_SB, RESET, Timeout, tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + + /* Send slave address */ + hi2c->Instance->DR = I2C_7BIT_ADD_WRITE(DevAddress); + + /* Wait until ADDR or AF flag are set */ + /* Get tick */ + tickstart = HAL_GetTick(); + + tmp1 = __HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_ADDR); + tmp2 = __HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_AF); + tmp3 = hi2c->State; + while((tmp1 == RESET) && (tmp2 == RESET) && (tmp3 != HAL_I2C_STATE_TIMEOUT)) + { + if((Timeout == 0U)||((HAL_GetTick() - tickstart ) > Timeout)) + { + hi2c->State = HAL_I2C_STATE_TIMEOUT; + } + tmp1 = __HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_ADDR); + tmp2 = __HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_AF); + tmp3 = hi2c->State; + } + + hi2c->State = HAL_I2C_STATE_READY; + + /* Check if the ADDR flag has been set */ + if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_ADDR) == SET) + { + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + + /* Clear ADDR Flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + /* Wait until BUSY flag is reset */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + + hi2c->State = HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_OK; + } + else + { + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + + /* Clear AF Flag */ + __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF); + + /* Wait until BUSY flag is reset */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + } + }while(I2C_Trials++ < Trials); + + hi2c->State = HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_ERROR; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief This function handles I2C event interrupt request. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @retval None + */ +void HAL_I2C_EV_IRQHandler(I2C_HandleTypeDef *hi2c) +{ + uint32_t sr2itflags = READ_REG(hi2c->Instance->SR2); + uint32_t sr1itflags = READ_REG(hi2c->Instance->SR1); + uint32_t itsources = READ_REG(hi2c->Instance->CR2); + + uint32_t CurrentMode = hi2c->Mode; + + /* Master or Memory mode selected */ + if((CurrentMode == HAL_I2C_MODE_MASTER) || (CurrentMode == HAL_I2C_MODE_MEM)) + { + /* SB Set ----------------------------------------------------------------*/ + if(((sr1itflags & I2C_FLAG_SB) != RESET) && ((itsources & I2C_IT_EVT) != RESET)) + { + I2C_Master_SB(hi2c); + } + /* ADD10 Set -------------------------------------------------------------*/ + else if(((sr1itflags & I2C_FLAG_ADD10) != RESET) && ((itsources & I2C_IT_EVT) != RESET)) + { + I2C_Master_ADD10(hi2c); + } + /* ADDR Set --------------------------------------------------------------*/ + else if(((sr1itflags & I2C_FLAG_ADDR) != RESET) && ((itsources & I2C_IT_EVT) != RESET)) + { + I2C_Master_ADDR(hi2c); + } + + /* I2C in mode Transmitter -----------------------------------------------*/ + if((sr2itflags & I2C_FLAG_TRA) != RESET) + { + /* TXE set and BTF reset -----------------------------------------------*/ + if(((sr1itflags & I2C_FLAG_TXE) != RESET) && ((itsources & I2C_IT_BUF) != RESET) && ((sr1itflags & I2C_FLAG_BTF) == RESET)) + { + I2C_MasterTransmit_TXE(hi2c); + } + /* BTF set -------------------------------------------------------------*/ + else if(((sr1itflags & I2C_FLAG_BTF) != RESET) && ((itsources & I2C_IT_EVT) != RESET)) + { + I2C_MasterTransmit_BTF(hi2c); + } + } + /* I2C in mode Receiver --------------------------------------------------*/ + else + { + /* RXNE set and BTF reset -----------------------------------------------*/ + if(((sr1itflags & I2C_FLAG_RXNE) != RESET) && ((itsources & I2C_IT_BUF) != RESET) && ((sr1itflags & I2C_FLAG_BTF) == RESET)) + { + I2C_MasterReceive_RXNE(hi2c); + } + /* BTF set -------------------------------------------------------------*/ + else if(((sr1itflags & I2C_FLAG_BTF) != RESET) && ((itsources & I2C_IT_EVT) != RESET)) + { + I2C_MasterReceive_BTF(hi2c); + } + } + } + /* Slave mode selected */ + else + { + /* ADDR set --------------------------------------------------------------*/ + if(((sr1itflags & I2C_FLAG_ADDR) != RESET) && ((itsources & I2C_IT_EVT) != RESET)) + { + I2C_Slave_ADDR(hi2c); + } + /* STOPF set --------------------------------------------------------------*/ + else if(((sr1itflags & I2C_FLAG_STOPF) != RESET) && ((itsources & I2C_IT_EVT) != RESET)) + { + I2C_Slave_STOPF(hi2c); + } + /* I2C in mode Transmitter -----------------------------------------------*/ + else if((sr2itflags & I2C_FLAG_TRA) != RESET) + { + /* TXE set and BTF reset -----------------------------------------------*/ + if(((sr1itflags & I2C_FLAG_TXE) != RESET) && ((itsources & I2C_IT_BUF) != RESET) && ((sr1itflags & I2C_FLAG_BTF) == RESET)) + { + I2C_SlaveTransmit_TXE(hi2c); + } + /* BTF set -------------------------------------------------------------*/ + else if(((sr1itflags & I2C_FLAG_BTF) != RESET) && ((itsources & I2C_IT_EVT) != RESET)) + { + I2C_SlaveTransmit_BTF(hi2c); + } + } + /* I2C in mode Receiver --------------------------------------------------*/ + else + { + /* RXNE set and BTF reset ----------------------------------------------*/ + if(((sr1itflags & I2C_FLAG_RXNE) != RESET) && ((itsources & I2C_IT_BUF) != RESET) && ((sr1itflags & I2C_FLAG_BTF) == RESET)) + { + I2C_SlaveReceive_RXNE(hi2c); + } + /* BTF set -------------------------------------------------------------*/ + else if(((sr1itflags & I2C_FLAG_BTF) != RESET) && ((itsources & I2C_IT_EVT) != RESET)) + { + I2C_SlaveReceive_BTF(hi2c); + } + } + } +} + +/** + * @brief This function handles I2C error interrupt request. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @retval None + */ +void HAL_I2C_ER_IRQHandler(I2C_HandleTypeDef *hi2c) +{ + uint32_t tmp1 = 0U, tmp2 = 0U, tmp3 = 0U, tmp4 = 0U; + uint32_t sr1itflags = READ_REG(hi2c->Instance->SR1); + uint32_t itsources = READ_REG(hi2c->Instance->CR2); + + /* I2C Bus error interrupt occurred ----------------------------------------*/ + if(((sr1itflags & I2C_FLAG_BERR) != RESET) && ((itsources & I2C_IT_ERR) != RESET)) + { + hi2c->ErrorCode |= HAL_I2C_ERROR_BERR; + + /* Clear BERR flag */ + __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_BERR); + } + + /* I2C Arbitration Loss error interrupt occurred ---------------------------*/ + if(((sr1itflags & I2C_FLAG_ARLO) != RESET) && ((itsources & I2C_IT_ERR) != RESET)) + { + hi2c->ErrorCode |= HAL_I2C_ERROR_ARLO; + + /* Clear ARLO flag */ + __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_ARLO); + } + + /* I2C Acknowledge failure error interrupt occurred ------------------------*/ + if(((sr1itflags & I2C_FLAG_AF) != RESET) && ((itsources & I2C_IT_ERR) != RESET)) + { + tmp1 = hi2c->Mode; + tmp2 = hi2c->XferCount; + tmp3 = hi2c->State; + tmp4 = hi2c->PreviousState; + if((tmp1 == HAL_I2C_MODE_SLAVE) && (tmp2 == 0U) && \ + ((tmp3 == HAL_I2C_STATE_BUSY_TX) || (tmp3 == HAL_I2C_STATE_BUSY_TX_LISTEN) || \ + ((tmp3 == HAL_I2C_STATE_LISTEN) && (tmp4 == I2C_STATE_SLAVE_BUSY_TX)))) + { + I2C_Slave_AF(hi2c); + } + else + { + hi2c->ErrorCode |= HAL_I2C_ERROR_AF; + + /* Do not generate a STOP in case of Slave receive non acknowledge during transfer (mean not at the end of transfer) */ + if(hi2c->Mode == HAL_I2C_MODE_MASTER) + { + /* Generate Stop */ + SET_BIT(hi2c->Instance->CR1,I2C_CR1_STOP); + } + + /* Clear AF flag */ + __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF); + } + } + + /* I2C Over-Run/Under-Run interrupt occurred -------------------------------*/ + if(((sr1itflags & I2C_FLAG_OVR) != RESET) && ((itsources & I2C_IT_ERR) != RESET)) + { + hi2c->ErrorCode |= HAL_I2C_ERROR_OVR; + /* Clear OVR flag */ + __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_OVR); + } + + /* Call the Error Callback in case of Error detected -----------------------*/ + if(hi2c->ErrorCode != HAL_I2C_ERROR_NONE) + { + I2C_ITError(hi2c); + } +} + +/** + * @brief Master Tx Transfer completed callback. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @retval None + */ +__weak void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c) +{ + /* Prevent unused argument(s) compilation warning */ + UNUSED(hi2c); + + /* NOTE : This function should not be modified, when the callback is needed, + the HAL_I2C_MasterTxCpltCallback can be implemented in the user file + */ +} + +/** + * @brief Master Rx Transfer completed callback. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @retval None + */ +__weak void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c) +{ + /* Prevent unused argument(s) compilation warning */ + UNUSED(hi2c); + + /* NOTE : This function should not be modified, when the callback is needed, + the HAL_I2C_MasterRxCpltCallback can be implemented in the user file + */ +} + +/** @brief Slave Tx Transfer completed callback. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @retval None + */ +__weak void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c) +{ + /* Prevent unused argument(s) compilation warning */ + UNUSED(hi2c); + + /* NOTE : This function should not be modified, when the callback is needed, + the HAL_I2C_SlaveTxCpltCallback can be implemented in the user file + */ +} + +/** + * @brief Slave Rx Transfer completed callback. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @retval None + */ +__weak void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c) +{ + /* Prevent unused argument(s) compilation warning */ + UNUSED(hi2c); + + /* NOTE : This function should not be modified, when the callback is needed, + the HAL_I2C_SlaveRxCpltCallback can be implemented in the user file + */ +} + +/** + * @brief Slave Address Match callback. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param TransferDirection Master request Transfer Direction (Write/Read), value of @ref I2C_XferOptions_definition + * @param AddrMatchCode Address Match Code + * @retval None + */ +__weak void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode) +{ + /* Prevent unused argument(s) compilation warning */ + UNUSED(hi2c); + UNUSED(TransferDirection); + UNUSED(AddrMatchCode); + + /* NOTE : This function should not be modified, when the callback is needed, + the HAL_I2C_AddrCallback can be implemented in the user file + */ +} + +/** + * @brief Listen Complete callback. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @retval None + */ +__weak void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef *hi2c) +{ + /* Prevent unused argument(s) compilation warning */ + UNUSED(hi2c); + + /* NOTE : This function should not be modified, when the callback is needed, + the HAL_I2C_ListenCpltCallback can be implemented in the user file + */ +} + +/** + * @brief Memory Tx Transfer completed callback. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @retval None + */ +__weak void HAL_I2C_MemTxCpltCallback(I2C_HandleTypeDef *hi2c) +{ + /* Prevent unused argument(s) compilation warning */ + UNUSED(hi2c); + + /* NOTE : This function should not be modified, when the callback is needed, + the HAL_I2C_MemTxCpltCallback can be implemented in the user file + */ +} + +/** + * @brief Memory Rx Transfer completed callback. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @retval None + */ +__weak void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c) +{ + /* Prevent unused argument(s) compilation warning */ + UNUSED(hi2c); + + /* NOTE : This function should not be modified, when the callback is needed, + the HAL_I2C_MemRxCpltCallback can be implemented in the user file + */ +} + +/** + * @brief I2C error callback. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @retval None + */ +__weak void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c) +{ + /* Prevent unused argument(s) compilation warning */ + UNUSED(hi2c); + + /* NOTE : This function should not be modified, when the callback is needed, + the HAL_I2C_ErrorCallback can be implemented in the user file + */ +} + +/** + * @brief I2C abort callback. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @retval None + */ +__weak void HAL_I2C_AbortCpltCallback(I2C_HandleTypeDef *hi2c) +{ + /* Prevent unused argument(s) compilation warning */ + UNUSED(hi2c); + + /* NOTE : This function should not be modified, when the callback is needed, + the HAL_I2C_AbortCpltCallback could be implemented in the user file + */ +} + +/** + * @} + */ + +/** @defgroup I2C_Exported_Functions_Group3 Peripheral State, Mode and Error functions + * @brief Peripheral State and Errors functions + * +@verbatim + =============================================================================== + ##### Peripheral State, Mode and Error functions ##### + =============================================================================== + [..] + This subsection permits to get in run-time the status of the peripheral + and the data flow. + +@endverbatim + * @{ + */ + +/** + * @brief Return the I2C handle state. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @retval HAL state + */ +HAL_I2C_StateTypeDef HAL_I2C_GetState(I2C_HandleTypeDef *hi2c) +{ + /* Return I2C handle state */ + return hi2c->State; +} + +/** + * @brief Return the I2C Master, Slave, Memory or no mode. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval HAL mode + */ +HAL_I2C_ModeTypeDef HAL_I2C_GetMode(I2C_HandleTypeDef *hi2c) +{ + return hi2c->Mode; +} + +/** + * @brief Return the I2C error code + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @retval I2C Error Code + */ +uint32_t HAL_I2C_GetError(I2C_HandleTypeDef *hi2c) +{ + return hi2c->ErrorCode; +} + +/** + * @} + */ + +/** + * @brief Handle TXE flag for Master + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_MasterTransmit_TXE(I2C_HandleTypeDef *hi2c) +{ + /* Declaration of temporary variables to prevent undefined behavior of volatile usage */ + uint32_t CurrentState = hi2c->State; + uint32_t CurrentMode = hi2c->Mode; + uint32_t CurrentXferOptions = hi2c->XferOptions; + + if((hi2c->XferSize == 0U) && (CurrentState == HAL_I2C_STATE_BUSY_TX)) + { + /* Call TxCpltCallback() directly if no stop mode is set */ + if((CurrentXferOptions != I2C_FIRST_AND_LAST_FRAME) && (CurrentXferOptions != I2C_LAST_FRAME) && (CurrentXferOptions != I2C_NO_OPTION_FRAME)) + { + __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + + hi2c->PreviousState = I2C_STATE_MASTER_BUSY_TX; + hi2c->Mode = HAL_I2C_MODE_NONE; + hi2c->State = HAL_I2C_STATE_READY; + + HAL_I2C_MasterTxCpltCallback(hi2c); + } + else /* Generate Stop condition then Call TxCpltCallback() */ + { + /* Disable EVT, BUF and ERR interrupt */ + __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State = HAL_I2C_STATE_READY; + + if(hi2c->Mode == HAL_I2C_MODE_MEM) + { + hi2c->Mode = HAL_I2C_MODE_NONE; + HAL_I2C_MemTxCpltCallback(hi2c); + } + else + { + hi2c->Mode = HAL_I2C_MODE_NONE; + HAL_I2C_MasterTxCpltCallback(hi2c); + } + } + } + else if((CurrentState == HAL_I2C_STATE_BUSY_TX) || \ + ((CurrentMode == HAL_I2C_MODE_MEM) && (CurrentState == HAL_I2C_STATE_BUSY_RX))) + { + if(hi2c->XferCount == 0U) + { + /* Disable BUF interrupt */ + __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_BUF); + } + else + { + if(hi2c->Mode == HAL_I2C_MODE_MEM) + { + if(hi2c->EventCount == 0) + { + /* If Memory address size is 8Bit */ + if(hi2c->MemaddSize == I2C_MEMADD_SIZE_8BIT) + { + /* Send Memory Address */ + hi2c->Instance->DR = I2C_MEM_ADD_LSB(hi2c->Memaddress); + + hi2c->EventCount += 2; + } + /* If Memory address size is 16Bit */ + else + { + /* Send MSB of Memory Address */ + hi2c->Instance->DR = I2C_MEM_ADD_MSB(hi2c->Memaddress); + + hi2c->EventCount++; + } + } + else if(hi2c->EventCount == 1) + { + /* Send LSB of Memory Address */ + hi2c->Instance->DR = I2C_MEM_ADD_LSB(hi2c->Memaddress); + + hi2c->EventCount++; + } + else if(hi2c->EventCount == 2) + { + if(hi2c->State == HAL_I2C_STATE_BUSY_RX) + { + /* Generate Restart */ + hi2c->Instance->CR1 |= I2C_CR1_START; + } + else if(hi2c->State == HAL_I2C_STATE_BUSY_TX) + { + /* Write data to DR */ + hi2c->Instance->DR = (*hi2c->pBuffPtr++); + hi2c->XferCount--; + } + } + } + else + { + /* Write data to DR */ + hi2c->Instance->DR = (*hi2c->pBuffPtr++); + hi2c->XferCount--; + } + } + } + return HAL_OK; +} + +/** + * @brief Handle BTF flag for Master transmitter + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_MasterTransmit_BTF(I2C_HandleTypeDef *hi2c) +{ + /* Declaration of temporary variables to prevent undefined behavior of volatile usage */ + uint32_t CurrentXferOptions = hi2c->XferOptions; + + if(hi2c->State == HAL_I2C_STATE_BUSY_TX) + { + if(hi2c->XferCount != 0U) + { + /* Write data to DR */ + hi2c->Instance->DR = (*hi2c->pBuffPtr++); + hi2c->XferCount--; + } + else + { + /* Call TxCpltCallback() directly if no stop mode is set */ + if((CurrentXferOptions != I2C_FIRST_AND_LAST_FRAME) && (CurrentXferOptions != I2C_LAST_FRAME) && (CurrentXferOptions != I2C_NO_OPTION_FRAME)) + { + __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + + hi2c->PreviousState = I2C_STATE_MASTER_BUSY_TX; + hi2c->Mode = HAL_I2C_MODE_NONE; + hi2c->State = HAL_I2C_STATE_READY; + + HAL_I2C_MasterTxCpltCallback(hi2c); + } + else /* Generate Stop condition then Call TxCpltCallback() */ + { + /* Disable EVT, BUF and ERR interrupt */ + __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State = HAL_I2C_STATE_READY; + + if(hi2c->Mode == HAL_I2C_MODE_MEM) + { + hi2c->Mode = HAL_I2C_MODE_NONE; + + HAL_I2C_MemTxCpltCallback(hi2c); + } + else + { + hi2c->Mode = HAL_I2C_MODE_NONE; + + HAL_I2C_MasterTxCpltCallback(hi2c); + } + } + } + } + return HAL_OK; +} + +/** + * @brief Handle RXNE flag for Master + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_MasterReceive_RXNE(I2C_HandleTypeDef *hi2c) +{ + if(hi2c->State == HAL_I2C_STATE_BUSY_RX) + { + uint32_t tmp = 0U; + + tmp = hi2c->XferCount; + if(tmp > 3U) + { + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferCount--; + + if(hi2c->XferCount == 3) + { + /* Disable BUF interrupt, this help to treat correctly the last 4 bytes + on BTF subroutine */ + /* Disable BUF interrupt */ + __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_BUF); + } + } + else if((tmp == 1U) || (tmp == 0U)) + { + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + /* Disable EVT, BUF and ERR interrupt */ + __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferCount--; + + hi2c->State = HAL_I2C_STATE_READY; + hi2c->PreviousState = I2C_STATE_NONE; + + if(hi2c->Mode == HAL_I2C_MODE_MEM) + { + hi2c->Mode = HAL_I2C_MODE_NONE; + HAL_I2C_MemRxCpltCallback(hi2c); + } + else + { + hi2c->Mode = HAL_I2C_MODE_NONE; + HAL_I2C_MasterRxCpltCallback(hi2c); + } + } + } + return HAL_OK; +} + +/** + * @brief Handle BTF flag for Master receiver + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_MasterReceive_BTF(I2C_HandleTypeDef *hi2c) +{ + /* Declaration of temporary variables to prevent undefined behavior of volatile usage */ + uint32_t CurrentXferOptions = hi2c->XferOptions; + + if(hi2c->XferCount == 4U) + { + /* Disable BUF interrupt, this help to treat correctly the last 2 bytes + on BTF subroutine if there is a reception delay between N-1 and N byte */ + __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_BUF); + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferCount--; + } + else if(hi2c->XferCount == 3U) + { + /* Disable BUF interrupt, this help to treat correctly the last 2 bytes + on BTF subroutine if there is a reception delay between N-1 and N byte */ + __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_BUF); + + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferCount--; + } + else if(hi2c->XferCount == 2U) + { + /* Prepare next transfer or stop current transfer */ + if((CurrentXferOptions == I2C_NEXT_FRAME) || (CurrentXferOptions == I2C_FIRST_FRAME)) + { + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + /* Generate ReStart */ + hi2c->Instance->CR1 |= I2C_CR1_START; + } + else + { + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + } + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferCount--; + + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferCount--; + + /* Disable EVT and ERR interrupt */ + __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_ERR); + + hi2c->State = HAL_I2C_STATE_READY; + hi2c->PreviousState = I2C_STATE_NONE; + + if(hi2c->Mode == HAL_I2C_MODE_MEM) + { + hi2c->Mode = HAL_I2C_MODE_NONE; + + HAL_I2C_MemRxCpltCallback(hi2c); + } + else + { + hi2c->Mode = HAL_I2C_MODE_NONE; + + HAL_I2C_MasterRxCpltCallback(hi2c); + } + } + else + { + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferCount--; + } + return HAL_OK; +} + +/** + * @brief Handle SB flag for Master + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_Master_SB(I2C_HandleTypeDef *hi2c) +{ + if(hi2c->Mode == HAL_I2C_MODE_MEM) + { + if(hi2c->EventCount == 0U) + { + /* Send slave address */ + hi2c->Instance->DR = I2C_7BIT_ADD_WRITE(hi2c->Devaddress); + } + else + { + hi2c->Instance->DR = I2C_7BIT_ADD_READ(hi2c->Devaddress); + } + } + else + { + if(hi2c->Init.AddressingMode == I2C_ADDRESSINGMODE_7BIT) + { + /* Send slave 7 Bits address */ + if(hi2c->State == HAL_I2C_STATE_BUSY_TX) + { + hi2c->Instance->DR = I2C_7BIT_ADD_WRITE(hi2c->Devaddress); + } + else + { + hi2c->Instance->DR = I2C_7BIT_ADD_READ(hi2c->Devaddress); + } + } + else + { + if(hi2c->EventCount == 0U) + { + /* Send header of slave address */ + hi2c->Instance->DR = I2C_10BIT_HEADER_WRITE(hi2c->Devaddress); + } + else if(hi2c->EventCount == 1U) + { + /* Send header of slave address */ + hi2c->Instance->DR = I2C_10BIT_HEADER_READ(hi2c->Devaddress); + } + } + } + + return HAL_OK; +} + +/** + * @brief Handle ADD10 flag for Master + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_Master_ADD10(I2C_HandleTypeDef *hi2c) +{ + /* Send slave address */ + hi2c->Instance->DR = I2C_10BIT_ADDRESS(hi2c->Devaddress); + + return HAL_OK; +} + +/** + * @brief Handle ADDR flag for Master + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_Master_ADDR(I2C_HandleTypeDef *hi2c) +{ + /* Declaration of temporary variable to prevent undefined behavior of volatile usage */ + uint32_t CurrentMode = hi2c->Mode; + uint32_t CurrentXferOptions = hi2c->XferOptions; + uint32_t Prev_State = hi2c->PreviousState; + + if(hi2c->State == HAL_I2C_STATE_BUSY_RX) + { + if((hi2c->EventCount == 0U) && (CurrentMode == HAL_I2C_MODE_MEM)) + { + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + } + else if((hi2c->EventCount == 0U) && (hi2c->Init.AddressingMode == I2C_ADDRESSINGMODE_10BIT)) + { + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + /* Generate Restart */ + hi2c->Instance->CR1 |= I2C_CR1_START; + + hi2c->EventCount++; + } + else + { + if(hi2c->XferCount == 0U) + { + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + } + else if(hi2c->XferCount == 1U) + { + if(CurrentXferOptions == I2C_NO_OPTION_FRAME) + { + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + if((hi2c->Instance->CR2 & I2C_CR2_DMAEN) == I2C_CR2_DMAEN) + { + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + } + else + { + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + } + } + /* Prepare next transfer or stop current transfer */ + else if((CurrentXferOptions != I2C_FIRST_AND_LAST_FRAME) && (CurrentXferOptions != I2C_LAST_FRAME) \ + && (Prev_State != I2C_STATE_MASTER_BUSY_RX)) + { + if(hi2c->XferOptions != I2C_NEXT_FRAME) + { + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + } + else + { + /* Enable Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + } + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + } + else + { + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + } + } + else if(hi2c->XferCount == 2U) + { + if(hi2c->XferOptions != I2C_NEXT_FRAME) + { + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + /* Enable Pos */ + hi2c->Instance->CR1 |= I2C_CR1_POS; + } + else + { + /* Enable Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + } + + if((hi2c->Instance->CR2 & I2C_CR2_DMAEN) == I2C_CR2_DMAEN) + { + /* Enable Last DMA bit */ + hi2c->Instance->CR2 |= I2C_CR2_LAST; + } + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + } + else + { + /* Enable Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + if((hi2c->Instance->CR2 & I2C_CR2_DMAEN) == I2C_CR2_DMAEN) + { + /* Enable Last DMA bit */ + hi2c->Instance->CR2 |= I2C_CR2_LAST; + } + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + } + + /* Reset Event counter */ + hi2c->EventCount = 0U; + } + } + else + { + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + } + + return HAL_OK; +} + +/** + * @brief Handle TXE flag for Slave + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_SlaveTransmit_TXE(I2C_HandleTypeDef *hi2c) +{ + /* Declaration of temporary variables to prevent undefined behavior of volatile usage */ + uint32_t CurrentState = hi2c->State; + + if(hi2c->XferCount != 0U) + { + /* Write data to DR */ + hi2c->Instance->DR = (*hi2c->pBuffPtr++); + hi2c->XferCount--; + + if((hi2c->XferCount == 0U) && (CurrentState == HAL_I2C_STATE_BUSY_TX_LISTEN)) + { + /* Last Byte is received, disable Interrupt */ + __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_BUF); + + /* Set state at HAL_I2C_STATE_LISTEN */ + hi2c->PreviousState = I2C_STATE_SLAVE_BUSY_TX; + hi2c->State = HAL_I2C_STATE_LISTEN; + + /* Call the Tx complete callback to inform upper layer of the end of receive process */ + HAL_I2C_SlaveTxCpltCallback(hi2c); + } + } + return HAL_OK; +} + +/** + * @brief Handle BTF flag for Slave transmitter + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_SlaveTransmit_BTF(I2C_HandleTypeDef *hi2c) +{ + if(hi2c->XferCount != 0U) + { + /* Write data to DR */ + hi2c->Instance->DR = (*hi2c->pBuffPtr++); + hi2c->XferCount--; + } + return HAL_OK; +} + +/** + * @brief Handle RXNE flag for Slave + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_SlaveReceive_RXNE(I2C_HandleTypeDef *hi2c) +{ + /* Declaration of temporary variables to prevent undefined behavior of volatile usage */ + uint32_t CurrentState = hi2c->State; + + if(hi2c->XferCount != 0U) + { + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferCount--; + + if((hi2c->XferCount == 0U) && (CurrentState == HAL_I2C_STATE_BUSY_RX_LISTEN)) + { + /* Last Byte is received, disable Interrupt */ + __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_BUF); + + /* Set state at HAL_I2C_STATE_LISTEN */ + hi2c->PreviousState = I2C_STATE_SLAVE_BUSY_RX; + hi2c->State = HAL_I2C_STATE_LISTEN; + + /* Call the Rx complete callback to inform upper layer of the end of receive process */ + HAL_I2C_SlaveRxCpltCallback(hi2c); + } + } + return HAL_OK; +} + +/** + * @brief Handle BTF flag for Slave receiver + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_SlaveReceive_BTF(I2C_HandleTypeDef *hi2c) +{ + if(hi2c->XferCount != 0U) + { + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferCount--; + } + return HAL_OK; +} + +/** + * @brief Handle ADD flag for Slave + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_Slave_ADDR(I2C_HandleTypeDef *hi2c) +{ + uint8_t TransferDirection = I2C_DIRECTION_RECEIVE; + uint16_t SlaveAddrCode = 0U; + + /* Transfer Direction requested by Master */ + if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_TRA) == RESET) + { + TransferDirection = I2C_DIRECTION_TRANSMIT; + } + + if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_DUALF) == RESET) + { + SlaveAddrCode = hi2c->Init.OwnAddress1; + } + else + { + SlaveAddrCode = hi2c->Init.OwnAddress2; + } + + /* Call Slave Addr callback */ + HAL_I2C_AddrCallback(hi2c, TransferDirection, SlaveAddrCode); + + return HAL_OK; +} + +/** + * @brief Handle STOPF flag for Slave + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_Slave_STOPF(I2C_HandleTypeDef *hi2c) +{ + /* Declaration of temporary variable to prevent undefined behavior of volatile usage */ + uint32_t CurrentState = hi2c->State; + + /* Disable EVT, BUF and ERR interrupt */ + __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + + /* Clear STOPF flag */ + __HAL_I2C_CLEAR_STOPFLAG(hi2c); + + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + /* If a DMA is ongoing, Update handle size context */ + if((hi2c->Instance->CR2 & I2C_CR2_DMAEN) == I2C_CR2_DMAEN) + { + if((hi2c->State == HAL_I2C_STATE_BUSY_RX) || (hi2c->State == HAL_I2C_STATE_BUSY_RX_LISTEN)) + { + hi2c->XferCount = __HAL_DMA_GET_COUNTER(hi2c->hdmarx); + } + else + { + hi2c->XferCount = __HAL_DMA_GET_COUNTER(hi2c->hdmatx); + } + } + + /* All data are not transferred, so set error code accordingly */ + if(hi2c->XferCount != 0U) + { + /* Store Last receive data if any */ + if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) + { + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferCount--; + } + + /* Store Last receive data if any */ + if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_RXNE) == SET) + { + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + hi2c->XferCount--; + } + + /* Set ErrorCode corresponding to a Non-Acknowledge */ + hi2c->ErrorCode |= HAL_I2C_ERROR_AF; + } + + if(hi2c->ErrorCode != HAL_I2C_ERROR_NONE) + { + /* Call the corresponding callback to inform upper layer of End of Transfer */ + I2C_ITError(hi2c); + } + else + { + if((CurrentState == HAL_I2C_STATE_LISTEN ) || (CurrentState == HAL_I2C_STATE_BUSY_RX_LISTEN) || \ + (CurrentState == HAL_I2C_STATE_BUSY_TX_LISTEN)) + { + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State = HAL_I2C_STATE_READY; + hi2c->Mode = HAL_I2C_MODE_NONE; + + /* Call the Listen Complete callback, to inform upper layer of the end of Listen usecase */ + HAL_I2C_ListenCpltCallback(hi2c); + } + else + { + if((hi2c->PreviousState == I2C_STATE_SLAVE_BUSY_RX) || (CurrentState == HAL_I2C_STATE_BUSY_RX)) + { + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State = HAL_I2C_STATE_READY; + hi2c->Mode = HAL_I2C_MODE_NONE; + + HAL_I2C_SlaveRxCpltCallback(hi2c); + } + } + } + return HAL_OK; +} + +/** + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_Slave_AF(I2C_HandleTypeDef *hi2c) +{ + /* Declaration of temporary variables to prevent undefined behavior of volatile usage */ + uint32_t CurrentState = hi2c->State; + uint32_t CurrentXferOptions = hi2c->XferOptions; + + if(((CurrentXferOptions == I2C_FIRST_AND_LAST_FRAME) || (CurrentXferOptions == I2C_LAST_FRAME)) && \ + (CurrentState == HAL_I2C_STATE_LISTEN)) + { + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + + /* Disable EVT, BUF and ERR interrupt */ + __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + + /* Clear AF flag */ + __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF); + + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State = HAL_I2C_STATE_READY; + hi2c->Mode = HAL_I2C_MODE_NONE; + + /* Call the Listen Complete callback, to inform upper layer of the end of Listen usecase */ + HAL_I2C_ListenCpltCallback(hi2c); + } + else if(CurrentState == HAL_I2C_STATE_BUSY_TX) + { + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->PreviousState = I2C_STATE_SLAVE_BUSY_TX; + hi2c->State = HAL_I2C_STATE_READY; + hi2c->Mode = HAL_I2C_MODE_NONE; + + /* Disable EVT, BUF and ERR interrupt */ + __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); + + /* Clear AF flag */ + __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF); + + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + HAL_I2C_SlaveTxCpltCallback(hi2c); + } + else + { + /* Clear AF flag only */ + /* State Listen, but XferOptions == FIRST or NEXT */ + __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF); + } + + return HAL_OK; +} + +/** + * @brief I2C interrupts error process + * @param hi2c I2C handle. + * @retval None + */ +static void I2C_ITError(I2C_HandleTypeDef *hi2c) +{ + /* Declaration of temporary variable to prevent undefined behavior of volatile usage */ + uint32_t CurrentState = hi2c->State; + + if((CurrentState == HAL_I2C_STATE_BUSY_TX_LISTEN) || (CurrentState == HAL_I2C_STATE_BUSY_RX_LISTEN)) + { + /* keep HAL_I2C_STATE_LISTEN */ + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State = HAL_I2C_STATE_LISTEN; + } + else + { + /* If state is an abort treatment on going, don't change state */ + /* This change will be do later */ + if((hi2c->State != HAL_I2C_STATE_ABORT) && ((hi2c->Instance->CR2 & I2C_CR2_DMAEN) != I2C_CR2_DMAEN)) + { + hi2c->State = HAL_I2C_STATE_READY; + } + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->Mode = HAL_I2C_MODE_NONE; + } + + /* Disable Pos bit in I2C CR1 when error occurred in Master/Mem Receive IT Process */ + hi2c->Instance->CR1 &= ~I2C_CR1_POS; + + /* Abort DMA transfer */ + if((hi2c->Instance->CR2 & I2C_CR2_DMAEN) == I2C_CR2_DMAEN) + { + hi2c->Instance->CR2 &= ~I2C_CR2_DMAEN; + + if(hi2c->hdmatx->State != HAL_DMA_STATE_READY) + { + /* Set the DMA Abort callback : + will lead to call HAL_I2C_ErrorCallback() at end of DMA abort procedure */ + hi2c->hdmatx->XferAbortCallback = I2C_DMAAbort; + + if(HAL_DMA_Abort_IT(hi2c->hdmatx) != HAL_OK) + { + /* Disable I2C peripheral to prevent dummy data in buffer */ + __HAL_I2C_DISABLE(hi2c); + + hi2c->State = HAL_I2C_STATE_READY; + + /* Call Directly XferAbortCallback function in case of error */ + hi2c->hdmatx->XferAbortCallback(hi2c->hdmatx); + } + } + else + { + /* Set the DMA Abort callback : + will lead to call HAL_I2C_ErrorCallback() at end of DMA abort procedure */ + hi2c->hdmarx->XferAbortCallback = I2C_DMAAbort; + + if(HAL_DMA_Abort_IT(hi2c->hdmarx) != HAL_OK) + { + /* Store Last receive data if any */ + if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_RXNE) == SET) + { + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + } + + /* Disable I2C peripheral to prevent dummy data in buffer */ + __HAL_I2C_DISABLE(hi2c); + + hi2c->State = HAL_I2C_STATE_READY; + + /* Call Directly hi2c->hdmarx->XferAbortCallback function in case of error */ + hi2c->hdmarx->XferAbortCallback(hi2c->hdmarx); + } + } + } + else if(hi2c->State == HAL_I2C_STATE_ABORT) + { + hi2c->State = HAL_I2C_STATE_READY; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Store Last receive data if any */ + if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_RXNE) == SET) + { + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + } + + /* Disable I2C peripheral to prevent dummy data in buffer */ + __HAL_I2C_DISABLE(hi2c); + + /* Call the corresponding callback to inform upper layer of End of Transfer */ + HAL_I2C_AbortCpltCallback(hi2c); + } + else + { + /* Store Last receive data if any */ + if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_RXNE) == SET) + { + /* Read data from DR */ + (*hi2c->pBuffPtr++) = hi2c->Instance->DR; + } + + /* Call user error callback */ + HAL_I2C_ErrorCallback(hi2c); + } + /* STOP Flag is not set after a NACK reception */ + /* So may inform upper layer that listen phase is stopped */ + /* during NACK error treatment */ + if((hi2c->State == HAL_I2C_STATE_LISTEN) && ((hi2c->ErrorCode & HAL_I2C_ERROR_AF) == HAL_I2C_ERROR_AF)) + { + hi2c->XferOptions = I2C_NO_OPTION_FRAME; + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State = HAL_I2C_STATE_READY; + hi2c->Mode = HAL_I2C_MODE_NONE; + + /* Call the Listen Complete callback, to inform upper layer of the end of Listen usecase */ + HAL_I2C_ListenCpltCallback(hi2c); + } +} + +/** + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @param DevAddress Target device address The device 7 bits address value + * in datasheet must be shifted to the left before calling the interface + * @param Timeout Timeout duration + * @param Tickstart Tick start value + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_MasterRequestWrite(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Timeout, uint32_t Tickstart) +{ + /* Declaration of temporary variable to prevent undefined behavior of volatile usage */ + uint32_t CurrentXferOptions = hi2c->XferOptions; + + /* Generate Start condition if first transfer */ + if((CurrentXferOptions == I2C_FIRST_AND_LAST_FRAME) || (CurrentXferOptions == I2C_FIRST_FRAME) || (CurrentXferOptions == I2C_NO_OPTION_FRAME)) + { + /* Generate Start */ + hi2c->Instance->CR1 |= I2C_CR1_START; + } + else if(hi2c->PreviousState == I2C_STATE_MASTER_BUSY_RX) + { + /* Generate ReStart */ + hi2c->Instance->CR1 |= I2C_CR1_START; + } + + /* Wait until SB flag is set */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_SB, RESET, Timeout, Tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + + if(hi2c->Init.AddressingMode == I2C_ADDRESSINGMODE_7BIT) + { + /* Send slave address */ + hi2c->Instance->DR = I2C_7BIT_ADD_WRITE(DevAddress); + } + else + { + /* Send header of slave address */ + hi2c->Instance->DR = I2C_10BIT_HEADER_WRITE(DevAddress); + + /* Wait until ADD10 flag is set */ + if(I2C_WaitOnMasterAddressFlagUntilTimeout(hi2c, I2C_FLAG_ADD10, Timeout, Tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + + /* Send slave address */ + hi2c->Instance->DR = I2C_10BIT_ADDRESS(DevAddress); + } + + /* Wait until ADDR flag is set */ + if(I2C_WaitOnMasterAddressFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, Timeout, Tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + + return HAL_OK; +} + +/** + * @brief Master sends target device address for read request. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @param DevAddress Target device address The device 7 bits address value + * in datasheet must be shifted to the left before calling the interface + * @param Timeout Timeout duration + * @param Tickstart Tick start value + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_MasterRequestRead(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Timeout, uint32_t Tickstart) +{ + /* Declaration of temporary variable to prevent undefined behavior of volatile usage */ + uint32_t CurrentXferOptions = hi2c->XferOptions; + + /* Enable Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + /* Generate Start condition if first transfer */ + if((CurrentXferOptions == I2C_FIRST_AND_LAST_FRAME) || (CurrentXferOptions == I2C_FIRST_FRAME) || (CurrentXferOptions == I2C_NO_OPTION_FRAME)) + { + /* Generate Start */ + hi2c->Instance->CR1 |= I2C_CR1_START; + } + else if(hi2c->PreviousState == I2C_STATE_MASTER_BUSY_TX) + { + /* Generate ReStart */ + hi2c->Instance->CR1 |= I2C_CR1_START; + } + + /* Wait until SB flag is set */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_SB, RESET, Timeout, Tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + + if(hi2c->Init.AddressingMode == I2C_ADDRESSINGMODE_7BIT) + { + /* Send slave address */ + hi2c->Instance->DR = I2C_7BIT_ADD_READ(DevAddress); + } + else + { + /* Send header of slave address */ + hi2c->Instance->DR = I2C_10BIT_HEADER_WRITE(DevAddress); + + /* Wait until ADD10 flag is set */ + if(I2C_WaitOnMasterAddressFlagUntilTimeout(hi2c, I2C_FLAG_ADD10, Timeout, Tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + + /* Send slave address */ + hi2c->Instance->DR = I2C_10BIT_ADDRESS(DevAddress); + + /* Wait until ADDR flag is set */ + if(I2C_WaitOnMasterAddressFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, Timeout, Tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + /* Generate Restart */ + hi2c->Instance->CR1 |= I2C_CR1_START; + + /* Wait until SB flag is set */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_SB, RESET, Timeout, Tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + + /* Send header of slave address */ + hi2c->Instance->DR = I2C_10BIT_HEADER_READ(DevAddress); + } + + /* Wait until ADDR flag is set */ + if(I2C_WaitOnMasterAddressFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, Timeout, Tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + + return HAL_OK; +} + +/** + * @brief Master sends target device address followed by internal memory address for write request. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @param DevAddress Target device address + * @param MemAddress Internal memory address + * @param MemAddSize Size of internal memory address + * @param Timeout Timeout duration + * @param Tickstart Tick start value + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_RequestMemoryWrite(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint32_t Timeout, uint32_t Tickstart) +{ + /* Generate Start */ + hi2c->Instance->CR1 |= I2C_CR1_START; + + /* Wait until SB flag is set */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_SB, RESET, Timeout, Tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + + /* Send slave address */ + hi2c->Instance->DR = I2C_7BIT_ADD_WRITE(DevAddress); + + /* Wait until ADDR flag is set */ + if(I2C_WaitOnMasterAddressFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, Timeout, Tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + /* Wait until TXE flag is set */ + if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, Tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + + /* If Memory address size is 8Bit */ + if(MemAddSize == I2C_MEMADD_SIZE_8BIT) + { + /* Send Memory Address */ + hi2c->Instance->DR = I2C_MEM_ADD_LSB(MemAddress); + } + /* If Memory address size is 16Bit */ + else + { + /* Send MSB of Memory Address */ + hi2c->Instance->DR = I2C_MEM_ADD_MSB(MemAddress); + + /* Wait until TXE flag is set */ + if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, Tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + + /* Send LSB of Memory Address */ + hi2c->Instance->DR = I2C_MEM_ADD_LSB(MemAddress); + } + + return HAL_OK; +} + +/** + * @brief Master sends target device address followed by internal memory address for read request. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @param DevAddress Target device address + * @param MemAddress Internal memory address + * @param MemAddSize Size of internal memory address + * @param Timeout Timeout duration + * @param Tickstart Tick start value + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_RequestMemoryRead(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint32_t Timeout, uint32_t Tickstart) +{ + /* Enable Acknowledge */ + hi2c->Instance->CR1 |= I2C_CR1_ACK; + + /* Generate Start */ + hi2c->Instance->CR1 |= I2C_CR1_START; + + /* Wait until SB flag is set */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_SB, RESET, Timeout, Tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + + /* Send slave address */ + hi2c->Instance->DR = I2C_7BIT_ADD_WRITE(DevAddress); + + /* Wait until ADDR flag is set */ + if(I2C_WaitOnMasterAddressFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, Timeout, Tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + + /* Clear ADDR flag */ + __HAL_I2C_CLEAR_ADDRFLAG(hi2c); + + /* Wait until TXE flag is set */ + if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, Tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + + /* If Memory address size is 8Bit */ + if(MemAddSize == I2C_MEMADD_SIZE_8BIT) + { + /* Send Memory Address */ + hi2c->Instance->DR = I2C_MEM_ADD_LSB(MemAddress); + } + /* If Memory address size is 16Bit */ + else + { + /* Send MSB of Memory Address */ + hi2c->Instance->DR = I2C_MEM_ADD_MSB(MemAddress); + + /* Wait until TXE flag is set */ + if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, Tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + + /* Send LSB of Memory Address */ + hi2c->Instance->DR = I2C_MEM_ADD_LSB(MemAddress); + } + + /* Wait until TXE flag is set */ + if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, Tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + + /* Generate Restart */ + hi2c->Instance->CR1 |= I2C_CR1_START; + + /* Wait until SB flag is set */ + if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_SB, RESET, Timeout, Tickstart) != HAL_OK) + { + return HAL_TIMEOUT; + } + + /* Send slave address */ + hi2c->Instance->DR = I2C_7BIT_ADD_READ(DevAddress); + + /* Wait until ADDR flag is set */ + if(I2C_WaitOnMasterAddressFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, Timeout, Tickstart) != HAL_OK) + { + if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) + { + return HAL_ERROR; + } + else + { + return HAL_TIMEOUT; + } + } + + return HAL_OK; +} + +/** + * @brief DMA I2C process complete callback. + * @param hdma DMA handle + * @retval None + */ +static void I2C_DMAXferCplt(DMA_HandleTypeDef *hdma) +{ + I2C_HandleTypeDef* hi2c = (I2C_HandleTypeDef*)((DMA_HandleTypeDef*)hdma)->Parent; + + /* Declaration of temporary variable to prevent undefined behavior of volatile usage */ + uint32_t CurrentState = hi2c->State; + uint32_t CurrentMode = hi2c->Mode; + + if((CurrentState == HAL_I2C_STATE_BUSY_TX) || ((CurrentState == HAL_I2C_STATE_BUSY_RX) && (CurrentMode == HAL_I2C_MODE_SLAVE))) + { + /* Disable DMA Request */ + hi2c->Instance->CR2 &= ~I2C_CR2_DMAEN; + + hi2c->XferCount = 0U; + + /* Enable EVT and ERR interrupt */ + __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_ERR); + } + else + { + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + + /* Disable Last DMA */ + hi2c->Instance->CR2 &= ~I2C_CR2_LAST; + + /* Disable DMA Request */ + hi2c->Instance->CR2 &= ~I2C_CR2_DMAEN; + + hi2c->XferCount = 0U; + + /* Check if Errors has been detected during transfer */ + if(hi2c->ErrorCode != HAL_I2C_ERROR_NONE) + { + HAL_I2C_ErrorCallback(hi2c); + } + else + { + hi2c->State = HAL_I2C_STATE_READY; + + if(hi2c->Mode == HAL_I2C_MODE_MEM) + { + hi2c->Mode = HAL_I2C_MODE_NONE; + + HAL_I2C_MemRxCpltCallback(hi2c); + } + else + { + hi2c->Mode = HAL_I2C_MODE_NONE; + + HAL_I2C_MasterRxCpltCallback(hi2c); + } + } + } +} + +/** + * @brief DMA I2C communication error callback. + * @param hdma DMA handle + * @retval None + */ +static void I2C_DMAError(DMA_HandleTypeDef *hdma) +{ + I2C_HandleTypeDef* hi2c = (I2C_HandleTypeDef*)((DMA_HandleTypeDef*)hdma)->Parent; + + /* Ignore DMA FIFO error */ + if(HAL_DMA_GetError(hdma) != HAL_DMA_ERROR_FE) + { + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + hi2c->XferCount = 0U; + + hi2c->State = HAL_I2C_STATE_READY; + hi2c->Mode = HAL_I2C_MODE_NONE; + + hi2c->ErrorCode |= HAL_I2C_ERROR_DMA; + + HAL_I2C_ErrorCallback(hi2c); + } +} + +/** + * @brief DMA I2C communication abort callback + * (To be called at end of DMA Abort procedure). + * @param hdma DMA handle. + * @retval None + */ +static void I2C_DMAAbort(DMA_HandleTypeDef *hdma) +{ + I2C_HandleTypeDef* hi2c = ( I2C_HandleTypeDef* )((DMA_HandleTypeDef* )hdma)->Parent; + + /* Disable Acknowledge */ + hi2c->Instance->CR1 &= ~I2C_CR1_ACK; + + hi2c->XferCount = 0U; + + /* Reset XferAbortCallback */ + hi2c->hdmatx->XferAbortCallback = NULL; + hi2c->hdmarx->XferAbortCallback = NULL; + + /* Check if come from abort from user */ + if(hi2c->State == HAL_I2C_STATE_ABORT) + { + hi2c->State = HAL_I2C_STATE_READY; + hi2c->Mode = HAL_I2C_MODE_NONE; + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + + /* Disable I2C peripheral to prevent dummy data in buffer */ + __HAL_I2C_DISABLE(hi2c); + + /* Call the corresponding callback to inform upper layer of End of Transfer */ + HAL_I2C_AbortCpltCallback(hi2c); + } + else + { + hi2c->State = HAL_I2C_STATE_READY; + hi2c->Mode = HAL_I2C_MODE_NONE; + + /* Disable I2C peripheral to prevent dummy data in buffer */ + __HAL_I2C_DISABLE(hi2c); + + /* Call the corresponding callback to inform upper layer of End of Transfer */ + HAL_I2C_ErrorCallback(hi2c); + } +} + +/** + * @brief This function handles I2C Communication Timeout. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @param Flag specifies the I2C flag to check. + * @param Status The new Flag status (SET or RESET). + * @param Timeout Timeout duration + * @param Tickstart Tick start value + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_WaitOnFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Flag, FlagStatus Status, uint32_t Timeout, uint32_t Tickstart) +{ + /* Wait until flag is set */ + while((__HAL_I2C_GET_FLAG(hi2c, Flag) ? SET : RESET) == Status) + { + /* Check for the Timeout */ + if(Timeout != HAL_MAX_DELAY) + { + if((Timeout == 0U)||((HAL_GetTick() - Tickstart ) > Timeout)) + { + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + hi2c->Mode = HAL_I2C_MODE_NONE; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + } + + return HAL_OK; +} + +/** + * @brief This function handles I2C Communication Timeout for Master addressing phase. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for I2C module + * @param Flag specifies the I2C flag to check. + * @param Timeout Timeout duration + * @param Tickstart Tick start value + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_WaitOnMasterAddressFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Flag, uint32_t Timeout, uint32_t Tickstart) +{ + while(__HAL_I2C_GET_FLAG(hi2c, Flag) == RESET) + { + if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_AF) == SET) + { + /* Generate Stop */ + hi2c->Instance->CR1 |= I2C_CR1_STOP; + + /* Clear AF Flag */ + __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF); + + hi2c->ErrorCode = HAL_I2C_ERROR_AF; + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_ERROR; + } + + /* Check for the Timeout */ + if(Timeout != HAL_MAX_DELAY) + { + if((Timeout == 0U)||((HAL_GetTick() - Tickstart ) > Timeout)) + { + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + } + return HAL_OK; +} + +/** + * @brief This function handles I2C Communication Timeout for specific usage of TXE flag. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param Timeout Timeout duration + * @param Tickstart Tick start value + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_WaitOnTXEFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout, uint32_t Tickstart) +{ + while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_TXE) == RESET) + { + /* Check if a NACK is detected */ + if(I2C_IsAcknowledgeFailed(hi2c) != HAL_OK) + { + return HAL_ERROR; + } + + /* Check for the Timeout */ + if(Timeout != HAL_MAX_DELAY) + { + if((Timeout == 0U) || ((HAL_GetTick()-Tickstart) > Timeout)) + { + hi2c->ErrorCode |= HAL_I2C_ERROR_TIMEOUT; + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + } + return HAL_OK; +} + +/** + * @brief This function handles I2C Communication Timeout for specific usage of BTF flag. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param Timeout Timeout duration + * @param Tickstart Tick start value + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_WaitOnBTFFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout, uint32_t Tickstart) +{ + while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == RESET) + { + /* Check if a NACK is detected */ + if(I2C_IsAcknowledgeFailed(hi2c) != HAL_OK) + { + return HAL_ERROR; + } + + /* Check for the Timeout */ + if(Timeout != HAL_MAX_DELAY) + { + if((Timeout == 0U) || ((HAL_GetTick()-Tickstart) > Timeout)) + { + hi2c->ErrorCode |= HAL_I2C_ERROR_TIMEOUT; + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + } + return HAL_OK; +} + +/** + * @brief This function handles I2C Communication Timeout for specific usage of STOP flag. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param Timeout Timeout duration + * @param Tickstart Tick start value + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_WaitOnSTOPFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout, uint32_t Tickstart) +{ + while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_STOPF) == RESET) + { + /* Check if a NACK is detected */ + if(I2C_IsAcknowledgeFailed(hi2c) != HAL_OK) + { + return HAL_ERROR; + } + + /* Check for the Timeout */ + if((Timeout == 0U) || ((HAL_GetTick()-Tickstart) > Timeout)) + { + hi2c->ErrorCode |= HAL_I2C_ERROR_TIMEOUT; + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + return HAL_OK; +} + +/** + * @brief This function handles I2C Communication Timeout for specific usage of RXNE flag. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @param Timeout Timeout duration + * @param Tickstart Tick start value + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_WaitOnRXNEFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout, uint32_t Tickstart) +{ + + while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_RXNE) == RESET) + { + /* Check if a STOPF is detected */ + if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_STOPF) == SET) + { + /* Clear STOP Flag */ + __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_STOPF); + + hi2c->ErrorCode = HAL_I2C_ERROR_NONE; + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_ERROR; + } + + /* Check for the Timeout */ + if((Timeout == 0U) || ((HAL_GetTick()-Tickstart) > Timeout)) + { + hi2c->ErrorCode |= HAL_I2C_ERROR_TIMEOUT; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_TIMEOUT; + } + } + return HAL_OK; +} + +/** + * @brief This function handles Acknowledge failed detection during an I2C Communication. + * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2C. + * @retval HAL status + */ +static HAL_StatusTypeDef I2C_IsAcknowledgeFailed(I2C_HandleTypeDef *hi2c) +{ + if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_AF) == SET) + { + /* Clear NACKF Flag */ + __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF); + + hi2c->ErrorCode = HAL_I2C_ERROR_AF; + hi2c->PreviousState = I2C_STATE_NONE; + hi2c->State= HAL_I2C_STATE_READY; + + /* Process Unlocked */ + __HAL_UNLOCK(hi2c); + + return HAL_ERROR; + } + return HAL_OK; +} +/** + * @} + */ + +#endif /* HAL_I2C_MODULE_ENABLED */ + +/** + * @} + */ + +/** + * @} + */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c_ex.c b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c_ex.c new file mode 100644 index 00000000..de8f1602 --- /dev/null +++ b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c_ex.c @@ -0,0 +1,204 @@ +/** + ****************************************************************************** + * @file stm32f4xx_hal_i2c_ex.c + * @author MCD Application Team + * @brief I2C Extension HAL module driver. + * This file provides firmware functions to manage the following + * functionalities of I2C extension peripheral: + * + Extension features functions + * + @verbatim + ============================================================================== + ##### I2C peripheral extension features ##### + ============================================================================== + + [..] Comparing to other previous devices, the I2C interface for STM32F427xx/437xx/ + 429xx/439xx devices contains the following additional features : + + (+) Possibility to disable or enable Analog Noise Filter + (+) Use of a configured Digital Noise Filter + + ##### How to use this driver ##### + ============================================================================== + [..] This driver provides functions to configure Noise Filter + (#) Configure I2C Analog noise filter using the function HAL_I2C_AnalogFilter_Config() + (#) Configure I2C Digital noise filter using the function HAL_I2C_DigitalFilter_Config() + + @endverbatim + ****************************************************************************** + * @attention + * + *

© COPYRIGHT(c) 2017 STMicroelectronics

+ * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of STMicroelectronics nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************** + */ + +/* Includes ------------------------------------------------------------------*/ +#include "stm32f4xx_hal.h" + +/** @addtogroup STM32F4xx_HAL_Driver + * @{ + */ + +/** @defgroup I2CEx I2CEx + * @brief I2C HAL module driver + * @{ + */ + +#ifdef HAL_I2C_MODULE_ENABLED + +#if defined(STM32F427xx) || defined(STM32F437xx) || defined(STM32F429xx) || defined(STM32F439xx) ||\ + defined(STM32F401xC) || defined(STM32F401xE) || defined(STM32F411xE) || defined(STM32F446xx) ||\ + defined(STM32F469xx) || defined(STM32F479xx) || defined(STM32F413xx) || defined(STM32F423xx) +/* Private typedef -----------------------------------------------------------*/ +/* Private define ------------------------------------------------------------*/ +/* Private macro -------------------------------------------------------------*/ +/* Private variables ---------------------------------------------------------*/ +/* Private function prototypes -----------------------------------------------*/ +/* Exported functions --------------------------------------------------------*/ +/** @defgroup I2CEx_Exported_Functions I2C Exported Functions + * @{ + */ + + +/** @defgroup I2CEx_Exported_Functions_Group1 Extension features functions + * @brief Extension features functions + * +@verbatim + =============================================================================== + ##### Extension features functions ##### + =============================================================================== + [..] This section provides functions allowing to: + (+) Configure Noise Filters + +@endverbatim + * @{ + */ + +/** + * @brief Configures I2C Analog noise filter. + * @param hi2c pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2Cx peripheral. + * @param AnalogFilter new state of the Analog filter. + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2CEx_ConfigAnalogFilter(I2C_HandleTypeDef *hi2c, uint32_t AnalogFilter) +{ + /* Check the parameters */ + assert_param(IS_I2C_ALL_INSTANCE(hi2c->Instance)); + assert_param(IS_I2C_ANALOG_FILTER(AnalogFilter)); + + if(hi2c->State == HAL_I2C_STATE_READY) + { + hi2c->State = HAL_I2C_STATE_BUSY; + + /* Disable the selected I2C peripheral */ + __HAL_I2C_DISABLE(hi2c); + + /* Reset I2Cx ANOFF bit */ + hi2c->Instance->FLTR &= ~(I2C_FLTR_ANOFF); + + /* Disable the analog filter */ + hi2c->Instance->FLTR |= AnalogFilter; + + __HAL_I2C_ENABLE(hi2c); + + hi2c->State = HAL_I2C_STATE_READY; + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @brief Configures I2C Digital noise filter. + * @param hi2c pointer to a I2C_HandleTypeDef structure that contains + * the configuration information for the specified I2Cx peripheral. + * @param DigitalFilter Coefficient of digital noise filter between 0x00 and 0x0F. + * @retval HAL status + */ +HAL_StatusTypeDef HAL_I2CEx_ConfigDigitalFilter(I2C_HandleTypeDef *hi2c, uint32_t DigitalFilter) +{ + uint16_t tmpreg = 0; + + /* Check the parameters */ + assert_param(IS_I2C_ALL_INSTANCE(hi2c->Instance)); + assert_param(IS_I2C_DIGITAL_FILTER(DigitalFilter)); + + if(hi2c->State == HAL_I2C_STATE_READY) + { + hi2c->State = HAL_I2C_STATE_BUSY; + + /* Disable the selected I2C peripheral */ + __HAL_I2C_DISABLE(hi2c); + + /* Get the old register value */ + tmpreg = hi2c->Instance->FLTR; + + /* Reset I2Cx DNF bit [3:0] */ + tmpreg &= ~(I2C_FLTR_DNF); + + /* Set I2Cx DNF coefficient */ + tmpreg |= DigitalFilter; + + /* Store the new register value */ + hi2c->Instance->FLTR = tmpreg; + + __HAL_I2C_ENABLE(hi2c); + + hi2c->State = HAL_I2C_STATE_READY; + + return HAL_OK; + } + else + { + return HAL_BUSY; + } +} + +/** + * @} + */ + +/** + * @} + */ +#endif /* STM32F427xx || STM32F429xx || STM32F437xx || STM32F439xx || STM32F401xC ||\ + STM32F401xE || STM32F446xx || STM32F469xx || STM32F479xx || STM32F413xx ||\ + STM32F423xx */ + +#endif /* HAL_I2C_MODULE_ENABLED */ +/** + * @} + */ + +/** + * @} + */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/Firmware/Board/v3/Inc/i2c.h b/Firmware/Board/v3/Inc/i2c.h new file mode 100644 index 00000000..f0b4cdec --- /dev/null +++ b/Firmware/Board/v3/Inc/i2c.h @@ -0,0 +1,91 @@ +/** + ****************************************************************************** + * File Name : I2C.h + * Description : This file provides code for the configuration + * of the I2C instances. + ****************************************************************************** + * This notice applies to any and all portions of this file + * that are not between comment pairs USER CODE BEGIN and + * USER CODE END. Other portions of this file, whether + * inserted by the user or by software development tools + * are owned by their respective copyright owners. + * + * Copyright (c) 2018 STMicroelectronics International N.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted, provided that the following conditions are met: + * + * 1. Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of STMicroelectronics nor the names of other + * contributors to this software may be used to endorse or promote products + * derived from this software without specific written permission. + * 4. This software, including modifications and/or derivative works of this + * software, must execute solely and exclusively on microcontroller or + * microprocessor devices manufactured by or for STMicroelectronics. + * 5. Redistribution and use of this software other than as permitted under + * this license is void and will automatically terminate your rights under + * this license. + * + * THIS SOFTWARE IS PROVIDED BY STMICROELECTRONICS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY + * RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT + * SHALL STMICROELECTRONICS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************** + */ +/* Define to prevent recursive inclusion -------------------------------------*/ +#ifndef __i2c_H +#define __i2c_H +#ifdef __cplusplus + extern "C" { +#endif + +/* Includes ------------------------------------------------------------------*/ +#include "stm32f4xx_hal.h" +#include "main.h" + +/* USER CODE BEGIN Includes */ + +/* USER CODE END Includes */ + +extern I2C_HandleTypeDef hi2c1; + +/* USER CODE BEGIN Private defines */ + +/* USER CODE END Private defines */ + +extern void _Error_Handler(char *, int); + +void MX_I2C1_Init(void); + +/* USER CODE BEGIN Prototypes */ + +/* USER CODE END Prototypes */ + +#ifdef __cplusplus +} +#endif +#endif /*__ i2c_H */ + +/** + * @} + */ + +/** + * @} + */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/Firmware/Board/v3/Inc/main.h b/Firmware/Board/v3/Inc/main.h index 4292821d..861b087e 100644 --- a/Firmware/Board/v3/Inc/main.h +++ b/Firmware/Board/v3/Inc/main.h @@ -54,6 +54,7 @@ /* Includes ------------------------------------------------------------------*/ /* USER CODE BEGIN Includes */ +#include "stm32f4xx_hal.h" #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 1 \ || HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 2 diff --git a/Firmware/Board/v3/Inc/stm32f4xx_hal_conf.h b/Firmware/Board/v3/Inc/stm32f4xx_hal_conf.h index d0f48f56..b3ef59aa 100644 --- a/Firmware/Board/v3/Inc/stm32f4xx_hal_conf.h +++ b/Firmware/Board/v3/Inc/stm32f4xx_hal_conf.h @@ -65,7 +65,7 @@ /* #define HAL_SRAM_MODULE_ENABLED */ /* #define HAL_SDRAM_MODULE_ENABLED */ /* #define HAL_HASH_MODULE_ENABLED */ -/* #define HAL_I2C_MODULE_ENABLED */ +#define HAL_I2C_MODULE_ENABLED /* #define HAL_I2S_MODULE_ENABLED */ /* #define HAL_IWDG_MODULE_ENABLED */ /* #define HAL_LTDC_MODULE_ENABLED */ diff --git a/Firmware/Board/v3/Inc/stm32f4xx_it.h b/Firmware/Board/v3/Inc/stm32f4xx_it.h index 1970c2a8..050334c4 100644 --- a/Firmware/Board/v3/Inc/stm32f4xx_it.h +++ b/Firmware/Board/v3/Inc/stm32f4xx_it.h @@ -54,9 +54,12 @@ void BusFault_Handler(void); void UsageFault_Handler(void); void DebugMon_Handler(void); void SysTick_Handler(void); +void DMA1_Stream0_IRQHandler(void); void DMA1_Stream2_IRQHandler(void); void DMA1_Stream4_IRQHandler(void); +void DMA1_Stream6_IRQHandler(void); void ADC_IRQHandler(void); +void I2C1_ER_IRQHandler(void); void TIM8_TRG_COM_TIM14_IRQHandler(void); void UART4_IRQHandler(void); void OTG_FS_IRQHandler(void); diff --git a/Firmware/Board/v3/Makefile b/Firmware/Board/v3/Makefile index edac2cb1..11a23ae6 100644 --- a/Firmware/Board/v3/Makefile +++ b/Firmware/Board/v3/Makefile @@ -58,7 +58,10 @@ Middlewares/Third_Party/FreeRTOS/Source/timers.c \ Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_tim.c \ Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_adc.c \ Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_gpio.c \ -Middlewares/Third_Party/FreeRTOS/Source/event_groups.c +Middlewares/Third_Party/FreeRTOS/Source/event_groups.c \ +Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c.c \ +Src/i2c.c \ +Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c_ex.c ASM_SOURCES = \ startup_stm32f405xx.s diff --git a/Firmware/Board/v3/Odrive.ioc b/Firmware/Board/v3/Odrive.ioc index ce8e1e2f..bfc49c18 100644 --- a/Firmware/Board/v3/Odrive.ioc +++ b/Firmware/Board/v3/Odrive.ioc @@ -78,9 +78,31 @@ ADC3.ScanConvMode=DISABLE CAN1.CalculateTimeBit=1142 CAN1.CalculateTimeQuantum=380.95238095238096 CAN1.IPParameters=CalculateTimeQuantum,CalculateTimeBit +Dma.I2C1_RX.2.Direction=DMA_PERIPH_TO_MEMORY +Dma.I2C1_RX.2.FIFOMode=DMA_FIFOMODE_DISABLE +Dma.I2C1_RX.2.Instance=DMA1_Stream0 +Dma.I2C1_RX.2.MemDataAlignment=DMA_MDATAALIGN_BYTE +Dma.I2C1_RX.2.MemInc=DMA_MINC_ENABLE +Dma.I2C1_RX.2.Mode=DMA_NORMAL +Dma.I2C1_RX.2.PeriphDataAlignment=DMA_PDATAALIGN_BYTE +Dma.I2C1_RX.2.PeriphInc=DMA_PINC_DISABLE +Dma.I2C1_RX.2.Priority=DMA_PRIORITY_LOW +Dma.I2C1_RX.2.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode +Dma.I2C1_TX.3.Direction=DMA_MEMORY_TO_PERIPH +Dma.I2C1_TX.3.FIFOMode=DMA_FIFOMODE_DISABLE +Dma.I2C1_TX.3.Instance=DMA1_Stream6 +Dma.I2C1_TX.3.MemDataAlignment=DMA_MDATAALIGN_BYTE +Dma.I2C1_TX.3.MemInc=DMA_MINC_ENABLE +Dma.I2C1_TX.3.Mode=DMA_NORMAL +Dma.I2C1_TX.3.PeriphDataAlignment=DMA_PDATAALIGN_BYTE +Dma.I2C1_TX.3.PeriphInc=DMA_PINC_DISABLE +Dma.I2C1_TX.3.Priority=DMA_PRIORITY_LOW +Dma.I2C1_TX.3.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode Dma.Request0=UART4_RX Dma.Request1=UART4_TX -Dma.RequestsNb=2 +Dma.Request2=I2C1_RX +Dma.Request3=I2C1_TX +Dma.RequestsNb=4 Dma.UART4_RX.0.Direction=DMA_PERIPH_TO_MEMORY Dma.UART4_RX.0.FIFOMode=DMA_FIFOMODE_DISABLE Dma.UART4_RX.0.Instance=DMA1_Stream2 @@ -109,6 +131,8 @@ FREERTOS.Tasks01=defaultTask,0,256,StartDefaultTask,Default,NULL,Dynamic,NULL,NU FREERTOS.configCHECK_FOR_STACK_OVERFLOW=1 FREERTOS.configTOTAL_HEAP_SIZE=65536 File.Version=6 +I2C1.IPParameters=OwnAddress +I2C1.OwnAddress=12 KeepUserPlacement=true Mcu.Family=STM32F4 Mcu.IP0=ADC1 @@ -121,6 +145,7 @@ Mcu.IP14=TIM8 Mcu.IP15=UART4 Mcu.IP16=USB_DEVICE Mcu.IP17=USB_OTG_FS +Mcu.IP18=I2C1 Mcu.IP2=ADC3 Mcu.IP3=CAN1 Mcu.IP4=DMA @@ -196,11 +221,15 @@ MxCube.Version=4.24.0 MxDb.Version=DB.4.0.240 NVIC.ADC_IRQn=true\:5\:0\:false\:false\:true\:true\:true NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:true +NVIC.DMA1_Stream0_IRQn=true\:5\:0\:false\:false\:true\:true\:false NVIC.DMA1_Stream2_IRQn=true\:5\:0\:false\:false\:true\:true\:true NVIC.DMA1_Stream4_IRQn=true\:5\:0\:false\:false\:true\:true\:false +NVIC.DMA1_Stream6_IRQn=true\:5\:0\:false\:false\:true\:true\:false NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:true NVIC.EXTI2_IRQn=true\:0\:0\:false\:false\:false\:false\:true NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:true +NVIC.I2C1_ER_IRQn=true\:5\:0\:false\:false\:true\:true\:true +NVIC.I2C1_EV_IRQn=true\:5\:0\:false\:false\:true\:true\:true NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:true NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:true NVIC.OTG_FS_IRQn=true\:5\:0\:false\:false\:true\:true\:true @@ -333,10 +362,12 @@ PB6.Signal=S_TIM4_CH1 PB7.GPIOParameters=GPIO_Label PB7.GPIO_Label=M1_ENC_B PB7.Signal=S_TIM4_CH2 -PB8.Mode=Master -PB8.Signal=CAN1_RX -PB9.Mode=Master -PB9.Signal=CAN1_TX +PB8.Locked=true +PB8.Signal=SharedStack_PB8 +PB8.Stacked=true +PB9.Locked=true +PB9.Signal=SharedStack_PB9 +PB9.Stacked=true PC0.GPIOParameters=GPIO_Label PC0.GPIO_Label=M0_IB PC0.Signal=ADCx_IN10 @@ -433,7 +464,7 @@ ProjectManager.StackSize=0x800 ProjectManager.TargetToolchain=Makefile ProjectManager.ToolChainLocation= ProjectManager.UnderRoot=false -ProjectManager.functionlistsort=1-MX_GPIO_Init-GPIO-false-HAL-true,2-MX_DMA_Init-DMA-false-HAL-true,3-MX_ADC1_Init-ADC1-false-HAL-true,4-MX_ADC2_Init-ADC2-false-HAL-true,5-MX_CAN1_Init-CAN1-false-HAL-true,6-MX_TIM1_Init-TIM1-false-HAL-true,7-MX_TIM8_Init-TIM8-false-HAL-true,8-MX_TIM3_Init-TIM3-false-HAL-true,9-MX_TIM4_Init-TIM4-false-HAL-true,10-MX_SPI3_Init-SPI3-false-HAL-true,11-MX_ADC3_Init-ADC3-false-HAL-true,12-SystemClock_Config-RCC-false-HAL-true,13-MX_TIM2_Init-TIM2-false-HAL-true,14-MX_USB_DEVICE_Init-USB_DEVICE-false-HAL-true,15-MX_UART4_Init-UART4-false-HAL-true +ProjectManager.functionlistsort=1-MX_GPIO_Init-GPIO-false-HAL-true,2-MX_DMA_Init-DMA-false-HAL-true,3-MX_ADC1_Init-ADC1-false-HAL-true,4-MX_ADC2_Init-ADC2-false-HAL-true,5-MX_TIM1_Init-TIM1-false-HAL-true,6-MX_TIM8_Init-TIM8-false-HAL-true,7-MX_TIM3_Init-TIM3-false-HAL-true,8-MX_TIM4_Init-TIM4-false-HAL-true,9-MX_SPI3_Init-SPI3-false-HAL-true,10-MX_ADC3_Init-ADC3-false-HAL-true,11-SystemClock_Config-RCC-false-HAL-true,12-MX_TIM2_Init-TIM2-false-HAL-true,13-MX_USB_DEVICE_Init-USB_DEVICE-false-HAL-true,14-MX_UART4_Init-UART4-false-HAL-true,15-MX_CAN1_Init-CAN1-false-HAL-true RCC.48MHZClocksFreq_Value=48000000 RCC.AHBFreq_Value=168000000 RCC.APB1CLKDivider=RCC_HCLK_DIV4 @@ -531,6 +562,12 @@ SH.SharedStack_PA0.ConfNb=2 SH.SharedStack_PA1.0=GPIO_Input+0 SH.SharedStack_PA1.1=UART4_RX,Asynchronous SH.SharedStack_PA1.ConfNb=2 +SH.SharedStack_PB8.0=CAN1_RX,Master +SH.SharedStack_PB8.1=I2C1_SCL +SH.SharedStack_PB8.ConfNb=2 +SH.SharedStack_PB9.0=CAN1_TX,Master +SH.SharedStack_PB9.1=I2C1_SDA +SH.SharedStack_PB9.ConfNb=2 SPI3.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_16 SPI3.CLKPhase=SPI_PHASE_2EDGE SPI3.CalculateBaudRate=2.625 MBits/s diff --git a/Firmware/Board/v3/Src/dma.c b/Firmware/Board/v3/Src/dma.c index a725a585..8ebfe19e 100644 --- a/Firmware/Board/v3/Src/dma.c +++ b/Firmware/Board/v3/Src/dma.c @@ -70,12 +70,18 @@ void MX_DMA_Init(void) __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA interrupt init */ + /* DMA1_Stream0_IRQn interrupt configuration */ + HAL_NVIC_SetPriority(DMA1_Stream0_IRQn, 5, 0); + HAL_NVIC_EnableIRQ(DMA1_Stream0_IRQn); /* DMA1_Stream2_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Stream2_IRQn, 5, 0); HAL_NVIC_EnableIRQ(DMA1_Stream2_IRQn); /* DMA1_Stream4_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 5, 0); HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn); + /* DMA1_Stream6_IRQn interrupt configuration */ + HAL_NVIC_SetPriority(DMA1_Stream6_IRQn, 5, 0); + HAL_NVIC_EnableIRQ(DMA1_Stream6_IRQn); } diff --git a/Firmware/Board/v3/Src/i2c.c b/Firmware/Board/v3/Src/i2c.c new file mode 100644 index 00000000..405c63fb --- /dev/null +++ b/Firmware/Board/v3/Src/i2c.c @@ -0,0 +1,198 @@ +/** + ****************************************************************************** + * File Name : I2C.c + * Description : This file provides code for the configuration + * of the I2C instances. + ****************************************************************************** + * This notice applies to any and all portions of this file + * that are not between comment pairs USER CODE BEGIN and + * USER CODE END. Other portions of this file, whether + * inserted by the user or by software development tools + * are owned by their respective copyright owners. + * + * Copyright (c) 2018 STMicroelectronics International N.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted, provided that the following conditions are met: + * + * 1. Redistribution of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of STMicroelectronics nor the names of other + * contributors to this software may be used to endorse or promote products + * derived from this software without specific written permission. + * 4. This software, including modifications and/or derivative works of this + * software, must execute solely and exclusively on microcontroller or + * microprocessor devices manufactured by or for STMicroelectronics. + * 5. Redistribution and use of this software other than as permitted under + * this license is void and will automatically terminate your rights under + * this license. + * + * THIS SOFTWARE IS PROVIDED BY STMICROELECTRONICS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A + * PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY + * RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT + * SHALL STMICROELECTRONICS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************** + */ + +/* Includes ------------------------------------------------------------------*/ +#include "i2c.h" + +#include "gpio.h" +#include "dma.h" + +/* USER CODE BEGIN 0 */ + +/* USER CODE END 0 */ + +I2C_HandleTypeDef hi2c1; +DMA_HandleTypeDef hdma_i2c1_rx; +DMA_HandleTypeDef hdma_i2c1_tx; + +/* I2C1 init function */ +void MX_I2C1_Init(void) +{ + + hi2c1.Instance = I2C1; + hi2c1.Init.ClockSpeed = 100000; + hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; + hi2c1.Init.OwnAddress1 = 24; + hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; + hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; + hi2c1.Init.OwnAddress2 = 0; + hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; + hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; + if (HAL_I2C_Init(&hi2c1) != HAL_OK) + { + _Error_Handler(__FILE__, __LINE__); + } + +} + +void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle) +{ + + GPIO_InitTypeDef GPIO_InitStruct; + if(i2cHandle->Instance==I2C1) + { + /* USER CODE BEGIN I2C1_MspInit 0 */ + + /* USER CODE END I2C1_MspInit 0 */ + + /**I2C1 GPIO Configuration + PB8 ------> I2C1_SCL + PB9 ------> I2C1_SDA + */ + GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9; + GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; + GPIO_InitStruct.Pull = GPIO_PULLUP; + GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; + GPIO_InitStruct.Alternate = GPIO_AF4_I2C1; + HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); + + /* I2C1 clock enable */ + __HAL_RCC_I2C1_CLK_ENABLE(); + + /* I2C1 DMA Init */ + /* I2C1_RX Init */ + hdma_i2c1_rx.Instance = DMA1_Stream0; + hdma_i2c1_rx.Init.Channel = DMA_CHANNEL_1; + hdma_i2c1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; + hdma_i2c1_rx.Init.PeriphInc = DMA_PINC_DISABLE; + hdma_i2c1_rx.Init.MemInc = DMA_MINC_ENABLE; + hdma_i2c1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; + hdma_i2c1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; + hdma_i2c1_rx.Init.Mode = DMA_NORMAL; + hdma_i2c1_rx.Init.Priority = DMA_PRIORITY_LOW; + hdma_i2c1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + if (HAL_DMA_Init(&hdma_i2c1_rx) != HAL_OK) + { + _Error_Handler(__FILE__, __LINE__); + } + + __HAL_LINKDMA(i2cHandle,hdmarx,hdma_i2c1_rx); + + /* I2C1_TX Init */ + hdma_i2c1_tx.Instance = DMA1_Stream6; + hdma_i2c1_tx.Init.Channel = DMA_CHANNEL_1; + hdma_i2c1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; + hdma_i2c1_tx.Init.PeriphInc = DMA_PINC_DISABLE; + hdma_i2c1_tx.Init.MemInc = DMA_MINC_ENABLE; + hdma_i2c1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; + hdma_i2c1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; + hdma_i2c1_tx.Init.Mode = DMA_NORMAL; + hdma_i2c1_tx.Init.Priority = DMA_PRIORITY_LOW; + hdma_i2c1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + if (HAL_DMA_Init(&hdma_i2c1_tx) != HAL_OK) + { + _Error_Handler(__FILE__, __LINE__); + } + + __HAL_LINKDMA(i2cHandle,hdmatx,hdma_i2c1_tx); + + /* I2C1 interrupt Init */ + HAL_NVIC_SetPriority(I2C1_EV_IRQn, 5, 0); + HAL_NVIC_EnableIRQ(I2C1_EV_IRQn); + HAL_NVIC_SetPriority(I2C1_ER_IRQn, 5, 0); + HAL_NVIC_EnableIRQ(I2C1_ER_IRQn); + /* USER CODE BEGIN I2C1_MspInit 1 */ + + /* USER CODE END I2C1_MspInit 1 */ + } +} + +void HAL_I2C_MspDeInit(I2C_HandleTypeDef* i2cHandle) +{ + + if(i2cHandle->Instance==I2C1) + { + /* USER CODE BEGIN I2C1_MspDeInit 0 */ + + /* USER CODE END I2C1_MspDeInit 0 */ + /* Peripheral clock disable */ + __HAL_RCC_I2C1_CLK_DISABLE(); + + /**I2C1 GPIO Configuration + PB8 ------> I2C1_SCL + PB9 ------> I2C1_SDA + */ + HAL_GPIO_DeInit(GPIOB, GPIO_PIN_8|GPIO_PIN_9); + + /* I2C1 DMA DeInit */ + HAL_DMA_DeInit(i2cHandle->hdmarx); + HAL_DMA_DeInit(i2cHandle->hdmatx); + + /* I2C1 interrupt Deinit */ + HAL_NVIC_DisableIRQ(I2C1_EV_IRQn); + HAL_NVIC_DisableIRQ(I2C1_ER_IRQn); + /* USER CODE BEGIN I2C1_MspDeInit 1 */ + + /* USER CODE END I2C1_MspDeInit 1 */ + } +} + +/* USER CODE BEGIN 1 */ + +/* USER CODE END 1 */ + +/** + * @} + */ + +/** + * @} + */ + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/Firmware/Board/v3/Src/main.c b/Firmware/Board/v3/Src/main.c index bafa4d3f..0d1bfdf8 100644 --- a/Firmware/Board/v3/Src/main.c +++ b/Firmware/Board/v3/Src/main.c @@ -61,6 +61,7 @@ /* USER CODE BEGIN Includes */ #include #include "freertos_vars.h" +#include "i2c.h" /* USER CODE END Includes */ /* Private variables ---------------------------------------------------------*/ @@ -183,6 +184,7 @@ int main(void) MX_TIM2_Init(); MX_UART4_Init(); /* USER CODE BEGIN 2 */ + MX_I2C1_Init(); //Required to use OC4 for ADC triggering. OC4_PWM_Override(&htim1); diff --git a/Firmware/Board/v3/Src/stm32f4xx_it.c b/Firmware/Board/v3/Src/stm32f4xx_it.c index cd96644a..5259fd41 100644 --- a/Firmware/Board/v3/Src/stm32f4xx_it.c +++ b/Firmware/Board/v3/Src/stm32f4xx_it.c @@ -54,6 +54,9 @@ extern PCD_HandleTypeDef hpcd_USB_OTG_FS; extern ADC_HandleTypeDef hadc1; extern ADC_HandleTypeDef hadc2; extern ADC_HandleTypeDef hadc3; +extern DMA_HandleTypeDef hdma_i2c1_rx; +extern DMA_HandleTypeDef hdma_i2c1_tx; +extern I2C_HandleTypeDef hi2c1; extern TIM_HandleTypeDef htim8; extern DMA_HandleTypeDef hdma_uart4_rx; extern DMA_HandleTypeDef hdma_uart4_tx; @@ -184,6 +187,20 @@ void SysTick_Handler(void) /* please refer to the startup file (startup_stm32f4xx.s). */ /******************************************************************************/ +/** +* @brief This function handles DMA1 stream0 global interrupt. +*/ +void DMA1_Stream0_IRQHandler(void) +{ + /* USER CODE BEGIN DMA1_Stream0_IRQn 0 */ + + /* USER CODE END DMA1_Stream0_IRQn 0 */ + HAL_DMA_IRQHandler(&hdma_i2c1_rx); + /* USER CODE BEGIN DMA1_Stream0_IRQn 1 */ + + /* USER CODE END DMA1_Stream0_IRQn 1 */ +} + /** * @brief This function handles DMA1 stream2 global interrupt. */ @@ -212,6 +229,20 @@ void DMA1_Stream4_IRQHandler(void) /* USER CODE END DMA1_Stream4_IRQn 1 */ } +/** +* @brief This function handles DMA1 stream6 global interrupt. +*/ +void DMA1_Stream6_IRQHandler(void) +{ + /* USER CODE BEGIN DMA1_Stream6_IRQn 0 */ + + /* USER CODE END DMA1_Stream6_IRQn 0 */ + HAL_DMA_IRQHandler(&hdma_i2c1_tx); + /* USER CODE BEGIN DMA1_Stream6_IRQn 1 */ + + /* USER CODE END DMA1_Stream6_IRQn 1 */ +} + /** * @brief This function handles ADC1, ADC2 and ADC3 global interrupts. */ @@ -238,6 +269,34 @@ void ADC_IRQHandler(void) /* USER CODE END ADC_IRQn 1 */ } +/** +* @brief This function handles I2C1 event interrupt. +*/ +void I2C1_EV_IRQHandler(void) +{ + /* USER CODE BEGIN I2C1_EV_IRQn 0 */ + + /* USER CODE END I2C1_EV_IRQn 0 */ + HAL_I2C_EV_IRQHandler(&hi2c1); + /* USER CODE BEGIN I2C1_EV_IRQn 1 */ + + /* USER CODE END I2C1_EV_IRQn 1 */ +} + +/** +* @brief This function handles I2C1 error interrupt. +*/ +void I2C1_ER_IRQHandler(void) +{ + /* USER CODE BEGIN I2C1_ER_IRQn 0 */ + + /* USER CODE END I2C1_ER_IRQn 0 */ + HAL_I2C_ER_IRQHandler(&hi2c1); + /* USER CODE BEGIN I2C1_ER_IRQn 1 */ + + /* USER CODE END I2C1_ER_IRQn 1 */ +} + /** * @brief This function handles TIM8 trigger and commutation interrupts and TIM14 global interrupt. */ From 78ae8fcd274065c06843656c76c5631d7e27ad7a Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 20 Apr 2018 21:09:05 -0700 Subject: [PATCH 136/215] modify I2C HAL don't treat incomplete data as an error allow customizing the I2C address --- .../v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c.c | 2 +- Firmware/Board/v3/Inc/i2c.h | 2 +- Firmware/Board/v3/Src/i2c.c | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c.c b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c.c index bc52bfd9..da185200 100644 --- a/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c.c +++ b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c.c @@ -4458,7 +4458,7 @@ static HAL_StatusTypeDef I2C_Slave_STOPF(I2C_HandleTypeDef *hi2c) } /* Set ErrorCode corresponding to a Non-Acknowledge */ - hi2c->ErrorCode |= HAL_I2C_ERROR_AF; + //hi2c->ErrorCode |= HAL_I2C_ERROR_AF; } if(hi2c->ErrorCode != HAL_I2C_ERROR_NONE) diff --git a/Firmware/Board/v3/Inc/i2c.h b/Firmware/Board/v3/Inc/i2c.h index f0b4cdec..f449b88a 100644 --- a/Firmware/Board/v3/Inc/i2c.h +++ b/Firmware/Board/v3/Inc/i2c.h @@ -69,7 +69,7 @@ extern I2C_HandleTypeDef hi2c1; extern void _Error_Handler(char *, int); -void MX_I2C1_Init(void); +void MX_I2C1_Init(uint8_t addr); /* USER CODE BEGIN Prototypes */ diff --git a/Firmware/Board/v3/Src/i2c.c b/Firmware/Board/v3/Src/i2c.c index 405c63fb..bae77f12 100644 --- a/Firmware/Board/v3/Src/i2c.c +++ b/Firmware/Board/v3/Src/i2c.c @@ -62,13 +62,13 @@ DMA_HandleTypeDef hdma_i2c1_rx; DMA_HandleTypeDef hdma_i2c1_tx; /* I2C1 init function */ -void MX_I2C1_Init(void) +void MX_I2C1_Init(uint8_t addr) { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; - hi2c1.Init.OwnAddress1 = 24; + hi2c1.Init.OwnAddress1 = addr << 1; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; @@ -114,7 +114,7 @@ void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle) hdma_i2c1_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_i2c1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_i2c1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; - hdma_i2c1_rx.Init.Mode = DMA_NORMAL; + hdma_i2c1_rx.Init.Mode = DMA_CIRCULAR; hdma_i2c1_rx.Init.Priority = DMA_PRIORITY_LOW; hdma_i2c1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; if (HAL_DMA_Init(&hdma_i2c1_rx) != HAL_OK) From c0ac611e4bdd64b103fb11fac3cc4394ade21b45 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 20 Apr 2018 21:15:33 -0700 Subject: [PATCH 137/215] implement protocol over I2C --- Firmware/Board/v3/Src/main.c | 1 - Firmware/MotorControl/board_config_v3.h | 8 +++ Firmware/MotorControl/main.cpp | 25 ++++++++ Firmware/MotorControl/odrive_main.h | 3 + Firmware/Tupfile.lua | 1 + Firmware/communication/communication.cpp | 11 ++++ Firmware/communication/interface_i2c.cpp | 80 ++++++++++++++++++++++++ Firmware/communication/interface_i2c.h | 25 ++++++++ 8 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 Firmware/communication/interface_i2c.cpp create mode 100644 Firmware/communication/interface_i2c.h diff --git a/Firmware/Board/v3/Src/main.c b/Firmware/Board/v3/Src/main.c index 0d1bfdf8..b07bf897 100644 --- a/Firmware/Board/v3/Src/main.c +++ b/Firmware/Board/v3/Src/main.c @@ -184,7 +184,6 @@ int main(void) MX_TIM2_Init(); MX_UART4_Init(); /* USER CODE BEGIN 2 */ - MX_I2C1_Init(); //Required to use OC4 for ADC triggering. OC4_PWM_Override(&htim1); diff --git a/Firmware/MotorControl/board_config_v3.h b/Firmware/MotorControl/board_config_v3.h index 791246de..46b7d5ae 100644 --- a/Firmware/MotorControl/board_config_v3.h +++ b/Firmware/MotorControl/board_config_v3.h @@ -117,4 +117,12 @@ const BoardHardwareConfig_t hw_configs[2] = { { #endif + +#define I2C_A0_PORT GPIO_1_GPIO_Port +#define I2C_A0_PIN GPIO_1_Pin +#define I2C_A1_PORT GPIO_2_GPIO_Port +#define I2C_A1_PIN GPIO_2_Pin +#define I2C_A2_PORT GPIO_3_GPIO_Port +#define I2C_A2_PIN GPIO_3_Pin + #endif // __BOARD_CONFIG_H diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index b7ec03d5..946c5e48 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -3,6 +3,8 @@ #include "odrive_main.h" #include "nvm_config.hpp" +#include + BoardConfig_t board_config; EncoderConfig_t encoder_configs[AXIS_COUNT]; ControllerConfig_t controller_configs[AXIS_COUNT]; @@ -68,6 +70,29 @@ int odrive_main(void) { // Load persistent configuration (or defaults) load_configuration(); + if (board_config.enable_i2c_instead_of_can) { + // Set up the direction GPIO as input + GPIO_InitTypeDef GPIO_InitStruct; + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = GPIO_NOPULL; + + GPIO_InitStruct.Pin = I2C_A0_PIN; + HAL_GPIO_Init(I2C_A0_PORT, &GPIO_InitStruct); + GPIO_InitStruct.Pin = I2C_A1_PIN; + HAL_GPIO_Init(I2C_A1_PORT, &GPIO_InitStruct); + GPIO_InitStruct.Pin = I2C_A2_PIN; + HAL_GPIO_Init(I2C_A2_PORT, &GPIO_InitStruct); + + osDelay(1); + i2c_stats_.addr = (0xD << 3); + i2c_stats_.addr |= HAL_GPIO_ReadPin(I2C_A0_PORT, I2C_A0_PIN) != GPIO_PIN_RESET ? 0x1 : 0; + i2c_stats_.addr |= HAL_GPIO_ReadPin(I2C_A1_PORT, I2C_A1_PIN) != GPIO_PIN_RESET ? 0x2 : 0; + i2c_stats_.addr |= HAL_GPIO_ReadPin(I2C_A2_PORT, I2C_A2_PIN) != GPIO_PIN_RESET ? 0x4 : 0; + MX_I2C1_Init(i2c_stats_.addr); + } else { + MX_CAN1_Init(); + } + // Construct all objects. for (size_t i = 0; i < AXIS_COUNT; ++i) { Encoder *encoder = new Encoder(hw_configs[i].encoder_config, diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index a66fcb74..269c2fa4 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -7,6 +7,8 @@ extern "C" { // STM specific includes #include // Sets up the correct chip specifc defines required by arm_math +#include +#include #define ARM_MATH_CM4 // TODO: might change in future board versions #include @@ -41,6 +43,7 @@ extern char serial_number_str[13]; // @brief general user configurable board configuration typedef struct { bool enable_uart = true; + bool enable_i2c_instead_of_can = true; float brake_resistance = 0.47f; // [ohm] float dc_bus_undervoltage_trip_level = 8.0f; //(&user_config_loaded)), make_protocol_ro_property("brake_resistor_armed", &brake_resistor_armed_), + make_protocol_object("i2c_stats", + make_protocol_ro_property("addr", &i2c_stats_.addr), + make_protocol_ro_property("addr_match_cnt", &i2c_stats_.addr_match_cnt), + make_protocol_ro_property("rx_cnt", &i2c_stats_.rx_cnt), + make_protocol_ro_property("error_cnt", &i2c_stats_.error_cnt) + ), make_protocol_object("config", make_protocol_property("brake_resistance", &board_config.brake_resistance), // TODO: changing this currently requires a reboot - fix this make_protocol_property("enable_uart", &board_config.enable_uart), + make_protocol_property("enable_i2c_instead_of_can" , &board_config.enable_i2c_instead_of_can), // requires a reboot make_protocol_property("dc_bus_undervoltage_trip_level", &board_config.dc_bus_undervoltage_trip_level), make_protocol_property("dc_bus_overvoltage_trip_level", &board_config.dc_bus_overvoltage_trip_level) ), @@ -144,6 +152,9 @@ void communication_task(void * ctx) { serve_on_uart(); serve_on_usb(); + if (board_config.enable_i2c_instead_of_can) { + serve_on_i2c(); + } for (;;) { osDelay(1000); // nothing to do diff --git a/Firmware/communication/interface_i2c.cpp b/Firmware/communication/interface_i2c.cpp new file mode 100644 index 00000000..525b4be0 --- /dev/null +++ b/Firmware/communication/interface_i2c.cpp @@ -0,0 +1,80 @@ + +#include "interface_i2c.h" +#include "protocol.hpp" + +#include + +#define I2C_RX_BUFFER_SIZE 128 +#define I2C_RX_BUFFER_PREAMBLE_SIZE 4 +#define I2C_TX_BUFFER_SIZE 128 + +I2CStats_t i2c_stats_ = {0}; + +static uint8_t i2c_rx_buffer[I2C_RX_BUFFER_PREAMBLE_SIZE + I2C_RX_BUFFER_SIZE]; +static uint8_t i2c_tx_buffer[I2C_TX_BUFFER_SIZE]; + +class I2CSender : public PacketSink { +public: + int process_packet(const uint8_t* buffer, size_t length) { + if (length >= 2 && (length - 2) <= sizeof(i2c_tx_buffer)) + memcpy(i2c_tx_buffer, buffer + 2, length - 2); + return 0; + } + size_t get_free_space() { return SIZE_MAX; } +} i2c1_packet_output; +BidirectionalPacketBasedChannel i2c1_channel(i2c1_packet_output); + +void serve_on_i2c() { + // CAN H = SDA + // CAN L = SCL + HAL_I2C_EnableListen_IT(&hi2c1); +} + +void i2c_handle_packet(I2C_HandleTypeDef *hi2c) { + size_t received = sizeof(i2c_rx_buffer) - hi2c->XferCount; + if (received > I2C_RX_BUFFER_PREAMBLE_SIZE) { + i2c_stats_.rx_cnt++; + + write_le(0, i2c_rx_buffer); // hallucinate seq-no (not needed for I2C) + i2c_rx_buffer[2] = i2c_rx_buffer[4]; // endpoint-id = I2C register address + i2c_rx_buffer[3] = i2c_rx_buffer[5] | 0x80; // MSB must be 1 + size_t expected_bytes = (TX_BUF_SIZE - 2) < I2C_TX_BUFFER_SIZE ? (TX_BUF_SIZE - 2) : I2C_TX_BUFFER_SIZE; + write_le(expected_bytes, i2c_rx_buffer + 4); // hallucinate maximum number of expected response bytes + + i2c1_channel.process_packet(i2c_rx_buffer, received); + + // reset receive buffer + hi2c->pBuffPtr = I2C_RX_BUFFER_PREAMBLE_SIZE + i2c_rx_buffer; + hi2c->XferCount = sizeof(i2c_rx_buffer) - I2C_RX_BUFFER_PREAMBLE_SIZE; + } + + + if (hi2c->State == HAL_I2C_STATE_BUSY_RX_LISTEN) + hi2c->State = HAL_I2C_STATE_LISTEN; +} + + +void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef *hi2c) { + i2c_handle_packet(hi2c); + // restart listening for address + HAL_I2C_EnableListen_IT(hi2c); +} + +void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode) { + i2c_stats_.addr_match_cnt += 1; + + i2c_handle_packet(hi2c); + + if (TransferDirection == I2C_DIRECTION_TRANSMIT) { + HAL_I2C_Slave_Sequential_Receive_IT(hi2c, + I2C_RX_BUFFER_PREAMBLE_SIZE + i2c_rx_buffer, + sizeof(i2c_rx_buffer) - I2C_RX_BUFFER_PREAMBLE_SIZE, I2C_FIRST_AND_LAST_FRAME); + } else { + HAL_I2C_Slave_Sequential_Transmit_IT(hi2c, i2c_tx_buffer, sizeof(i2c_tx_buffer), I2C_FIRST_AND_LAST_FRAME); + } +} + +void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c) { + if (hi2c->ErrorCode & (~HAL_I2C_ERROR_AF)) // ignore NACK errors + i2c_stats_.error_cnt += 1; +} diff --git a/Firmware/communication/interface_i2c.h b/Firmware/communication/interface_i2c.h new file mode 100644 index 00000000..db866912 --- /dev/null +++ b/Firmware/communication/interface_i2c.h @@ -0,0 +1,25 @@ +#ifndef __INTERFACE_I2C_HPP +#define __INTERFACE_I2C_HPP + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct I2CStats_t { + uint8_t addr; + uint32_t addr_match_cnt; + uint32_t rx_cnt; + uint32_t error_cnt; +}; + +extern I2CStats_t i2c_stats_; + +void serve_on_i2c(void); + +#ifdef __cplusplus +} +#endif + +#endif // __INTERFACE_I2C_HPP From b2fed7c64c7540cc835dc26ab5343bcb09fc08ab Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 19 Apr 2018 21:16:09 -0700 Subject: [PATCH 138/215] implement JSON to C++ header dump tool --- tools/odrive/discovery.py | 6 +++- tools/odrive/template_processor.py | 49 ++++++++++++++++++++++++++++++ tools/odrive_header_template.h.in | 34 +++++++++++++++++++++ tools/odrivetool | 14 +++++++++ 4 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 tools/odrive/template_processor.py create mode 100644 tools/odrive_header_template.h.in diff --git a/tools/odrive/discovery.py b/tools/odrive/discovery.py index a6536638..3137f0ae 100644 --- a/tools/odrive/discovery.py +++ b/tools/odrive/discovery.py @@ -53,7 +53,7 @@ def find_all(path, serial_number, except UnicodeDecodeError: printer("device responded on endpoint 0 with something that is not ASCII") return - printer("JSON: " + json_string) + printer("JSON: " + json_string.replace('{"name"', '\n{"name"')) printer("JSON checksum: 0x{:02X} 0x{:02X}".format(json_crc16 & 0xff, (json_crc16 >> 8) & 0xff)) try: json_data = json.loads(json_string) @@ -62,6 +62,10 @@ def find_all(path, serial_number, return json_data = {"name": "odrive", "members": json_data} obj = odrive.remote_object.RemoteObject(json_data, None, channel, printer) + + obj.__dict__['_json_data'] = json_data['members'] + obj.__dict__['_json_crc'] = json_crc16 + device_serial_number = format(obj.serial_number, 'x').upper() if hasattr(obj, 'serial_number') else "[unknown serial number]" if serial_number != None and device_serial_number != serial_number: printer("Ignoring device with serial number {}".format(device_serial_number)) diff --git a/tools/odrive/template_processor.py b/tools/odrive/template_processor.py new file mode 100644 index 00000000..757b6d6a --- /dev/null +++ b/tools/odrive/template_processor.py @@ -0,0 +1,49 @@ + +import jinja2 +import os +import json + +def get_flat_endpoint_list(json, prefix): + flat_list = [] + for item in json: + item = item.copy() + if 'type' in item: + if item['type'] in {'int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64', 'uint64'}: + item['type'] += '_t' + is_property = True + elif item['type'] in {'bool', 'float'}: + is_property = True + else: + is_property = False + if is_property: + item['name'] = prefix + item['name'] + flat_list.append(item) + if 'members' in item: + flat_list = flat_list + get_flat_endpoint_list(item['members'], prefix + item['name'] + '.') + return flat_list + +def generate_header(odrv, template_file, output_file): + json_data = odrv._json_data + json_crc = odrv._json_crc + + endpoints = get_flat_endpoint_list(json_data, '') + + env = jinja2.Environment( + #loader = jinja2.FileSystemLoader("/Data/Projects/") + #trim_blocks=True, + #lstrip_blocks=True + ) + + # Expose helper functions to jinja template code + #env.filters["delimit"] = camel_case_to_words + + # Load and render template + template = env.from_string(template_file.read()) + output = template.render( + json_crc=json_crc, + endpoints=endpoints, + output_name=os.path.basename(output_file.name) + ) + + # Output + output_file.write(output) diff --git a/tools/odrive_header_template.h.in b/tools/odrive_header_template.h.in new file mode 100644 index 00000000..392cddd6 --- /dev/null +++ b/tools/odrive_header_template.h.in @@ -0,0 +1,34 @@ +/* +* This file was autogenerated using the "odrivetool process-template" feature. +* +* The file matches a specific firmware version. If you add/remove/rename any +* properties exposed by the ODrive, this file needs to be regenerated, otherwise +* the ODrive will ignore all commands. +*/ + +#ifndef __ODRIVE_ENDPOINTS_HPP +#define __ODRIVE_ENDPOINTS_HPP +{% macro enum_name(endpoint) %}{{ endpoint.name | replace('.', '__') | upper }}{% endmacro %} + +namespace odrive { + +static constexpr const uint16_t json_crc = 0x{{ "%0x" | format(json_crc) }}; + +enum { {% for endpoint in endpoints %} + {{enum_name(endpoint)}} = {{endpoint.id}}, +{%- endfor %} +}; + +template +struct endpoint_type; + +{% for endpoint in endpoints -%} +template<> struct endpoint_type<{{enum_name(endpoint)}}> { typedef {{endpoint.type}} type; }; +{% endfor %} + +template +using endpoint_type_t = typename endpoint_type::type; + +} + +#endif __ODRIVE_ENDPOINTS_HPP diff --git a/tools/odrivetool b/tools/odrivetool index 901ebde3..eba8e829 100755 --- a/tools/odrivetool +++ b/tools/odrivetool @@ -6,6 +6,7 @@ ODrive command line utility from __future__ import print_function import sys import argparse +import os import odrive.discovery from odrive.utils import Logger, Event @@ -19,6 +20,7 @@ def print(*args, **kwargs): file = kwargs.get('file', sys.stdout) file.flush() if file is not None else sys.stdout.flush() +script_path=os.path.dirname(os.path.realpath(__file__)) ## Parse arguments ## parser = argparse.ArgumentParser(description='ODrive command line utility\n' @@ -36,6 +38,13 @@ shell_parser.add_argument("--no-ipython", action="store_true", dfu_parser = subparsers.add_parser('dfu', help="Upgrade the ODrive device firmware") dfu_parser.add_argument('file', metavar='HEX', help='The .hex file to be flashed. Make sure your firmware board version matches the actual board version.') +template_processor_parser = subparsers.add_parser('process-template', help="Process a jinja2 template, passing the ODrive's JSON data as data input") +template_processor_parser.add_argument("-t", "--template", type=argparse.FileType('r'), + help="the code template") +template_processor_parser.add_argument("-o", "--output", type=argparse.FileType('w'), default='-', + help="path of the generated output") +template_processor_parser.set_defaults(template = os.path.join(script_path, 'odrive_header_template.h.in')) + subparsers.add_parser('liveplotter', help="Upgrade the ODrive's Firmware") subparsers.add_parser('drv-status', help="Show status of the on-board DRV8301 chips (for debugging only)") subparsers.add_parser('rate-test', help="Estimate the average transmission bandwidth over USB") @@ -138,6 +147,11 @@ try: elif args.command == 'udev-setup': from odrive.utils import setup_udev_rules setup_udev_rules(logger) + + elif args.command == 'process-template': + from odrive.template_processor import generate_header + my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number) + generate_header(my_odrive, args.template, args.output) else: raise Exception("unknown command: " + args.command) From 2a2b3fbe4e2d45d22e12484c85abf97e95919b7f Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 24 Apr 2018 19:35:44 -0700 Subject: [PATCH 139/215] add example Arduino application --- ArduinoI2C/ArduinoI2C.ino | 56 +++++ ArduinoI2C/odrive.h | 143 +++++++++++ ArduinoI2C/odrive_endpoints.h | 444 ++++++++++++++++++++++++++++++++++ ArduinoI2C/type_traits.h | 267 ++++++++++++++++++++ 4 files changed, 910 insertions(+) create mode 100644 ArduinoI2C/ArduinoI2C.ino create mode 100644 ArduinoI2C/odrive.h create mode 100644 ArduinoI2C/odrive_endpoints.h create mode 100644 ArduinoI2C/type_traits.h diff --git a/ArduinoI2C/ArduinoI2C.ino b/ArduinoI2C/ArduinoI2C.ino new file mode 100644 index 00000000..39d491a7 --- /dev/null +++ b/ArduinoI2C/ArduinoI2C.ino @@ -0,0 +1,56 @@ + +#include +#include "odrive.h" + + +// See odrive.h for a description +bool I2C_transaction(uint8_t slave_addr, const uint8_t * tx_buffer, size_t tx_length, uint8_t * rx_buffer, size_t rx_length) { + // transmit + if (tx_buffer) { + Wire.beginTransmission(slave_addr); + if (Wire.write(tx_buffer, tx_length) != tx_length) + return false; + bool should_stop = !rx_buffer; + if (Wire.endTransmission(should_stop) != 0) + return false; + } + + // receive + if (rx_buffer) { + while(Wire.available()) Wire.read(); // flush input buffer + if (Wire.requestFrom(slave_addr, (uint8_t)rx_length, (uint8_t)true /* stop after receiving */) != rx_length) + return false; + for (size_t i = 0; i < rx_length; ++i) + rx_buffer[i] = Wire.read(); + } + + return true; +} + + +void setup() { + Wire.begin(); // join i2c bus (address optional for master) + Serial.begin(9600); +} + +byte odrive_num = 7; +byte x; + +void loop() { + bool success; + + float val; + success = odrive::read_property(odrive_num, &val); + if (success) { + Serial.println(val, HEX); + } else { + Serial.println("error"); + } + + success = odrive::write_property(odrive_num, x++); + if (!success) + Serial.println("error"); + + delay(500); +} + diff --git a/ArduinoI2C/odrive.h b/ArduinoI2C/odrive.h new file mode 100644 index 00000000..fb89ccb5 --- /dev/null +++ b/ArduinoI2C/odrive.h @@ -0,0 +1,143 @@ +/* +* ODrive I2C communication library +* This file implements I2C communication with the ODrive. +* +* - Implement the C function I2C_transaction to provide low level I2C access. +* - Use read_property() to read properties from the ODrive. +* - Use write_property() to modify properties on the ODrive. +* - Use endpoint_type_t to retrieve the underlying type +* of a given property. +* +* To regenerate the interface definitions, flash an ODrive with the new +* firmware, then run +* ../tools/odrivetool generate-code --output odrive_endpoints.h +*/ + + +#include +#include + +#include "odrive_endpoints.h" + +#ifdef __AVR__ +// AVR-GCC doesn't ship with the STL, so we use our own little excerpt +#include "type_traits.h" +#else +#include +#endif + + +extern "C" { + +/* @brief Send and receive data to/from an I2C slave +* +* This function carries out the following sequence: +* 1. generate a START condition +* 2. if the tx_buffer is not null: +* a. send 7-bit slave address (with the LSB 0) +* b. send all bytes in the tx_buffer +* 3. if both tx_buffer and rx_buffer are not null, generate a REPEATED START condition +* 4. if the rx_buffer is not null: +* a. send 7-bit slave address (with the LSB 1) +* b. read rx_length bytes into rx_buffer +* 5. send STOP condition +* +* @param slave_addr: 7-bit slave address (the MSB is ignored) +* @return true if all data was transmitted and received as requested by the caller, false otherwise +*/ +bool I2C_transaction(uint8_t slave_addr, const uint8_t * tx_buffer, size_t tx_length, uint8_t * rx_buffer, size_t rx_length); + +} + + +namespace odrive { + static constexpr const uint8_t i2c_addr = (0xD << 3); // write: 1101xxx0, read: 1101xxx1 + + template + using bit_width = std::integral_constant; + + template + using byte_width = std::integral_constant::value + 7) / 8>; + + + template + struct unsigned_int_of_size; + + template<> struct unsigned_int_of_size<32> { typedef uint32_t type; }; + + + template + typename std::enable_if::value, T>::type + read_le(const uint8_t buffer[byte_width::value]) { + T value = 0; + for (size_t i = 0; i < byte_width::value; ++i) + value |= (static_cast(buffer[i]) << (i << 3)); + return value; + } + + template + typename std::enable_if::value, T>::type + read_le(const uint8_t buffer[]) { + using T_Int = typename unsigned_int_of_size::value>::type; + T_Int value = read_le(buffer); + return *reinterpret_cast(&value); + } + + template + typename std::enable_if::value, void>::type + write_le(uint8_t buffer[byte_width::value], T value) { + for (size_t i = 0; i < byte_width::value; ++i) + buffer[i] = (value >> (i << 3)) & 0xff; + } + + template + typename std::enable_if::value, T>::type + write_le(uint8_t buffer[byte_width::value], T value) { + using T_Int = typename unsigned_int_of_size::value>::type; + write_le(buffer, *reinterpret_cast(&value)); + } + + /* @brief Read from an endpoint on the ODrive + * + * Usage example: + * float val; + * success = odrive::read_property(0, &val); + * + * @param num Selects the ODrive. For instance the value 4 selects + * the ODrive that has [A2, A1, A0] connected to [VCC, GND, GND]. + * @return true if the I2C transaction succeeded, false otherwise + */ + template + bool read_property(uint8_t num, endpoint_type_t* value) { + uint8_t i2c_tx_buffer[4]; + write_le(i2c_tx_buffer, IPropertyId); + write_le(i2c_tx_buffer + sizeof(i2c_tx_buffer) - 2, json_crc); + uint8_t i2c_rx_buffer[byte_width>::value]; + if (!I2C_transaction(i2c_addr + num, + i2c_tx_buffer, sizeof(i2c_tx_buffer), + i2c_rx_buffer, sizeof(i2c_rx_buffer))) + return false; + if (value) + *value = read_le>(i2c_rx_buffer); + return true; + } + + /* @brief Write to an endpoint on the ODrive + * + * Usage example: + * success = odrive::write_property(0, 10000); + * + * @param num Selects the ODrive. For instance the value 4 selects + * the ODrive that has [A2, A1, A0] connected to [VCC, GND, GND]. + * @return true if the I2C transaction succeeded, false otherwise + */ + template + bool write_property(uint8_t num, endpoint_type_t value) { + uint8_t i2c_tx_buffer[4 + byte_width>::value]; + write_le(i2c_tx_buffer, IPropertyId); + write_le>(i2c_tx_buffer + 2, value); + write_le(i2c_tx_buffer + sizeof(i2c_tx_buffer) - 2, json_crc); + return I2C_transaction(i2c_addr + num, i2c_tx_buffer, sizeof(i2c_tx_buffer), nullptr, 0); + } + +} diff --git a/ArduinoI2C/odrive_endpoints.h b/ArduinoI2C/odrive_endpoints.h new file mode 100644 index 00000000..e8e4ee98 --- /dev/null +++ b/ArduinoI2C/odrive_endpoints.h @@ -0,0 +1,444 @@ +/* +* This file was autogenerated using the "odrivetool generate-code" feature. +* +* The file matches a specific firmware version. If you add/remove/rename any +* properties exposed by the ODrive, this file needs to be regenerated, otherwise +* the ODrive will ignore all commands. +*/ + +#ifndef __ODRIVE_ENDPOINTS_HPP +#define __ODRIVE_ENDPOINTS_HPP + + +namespace odrive { + +static constexpr const uint16_t json_crc = 0x7199; + +enum { + VBUS_VOLTAGE = 1, + SERIAL_NUMBER = 2, + HW_VERSION_MAJOR = 3, + HW_VERSION_MINOR = 4, + HW_VERSION_VARIANT = 5, + FW_VERSION_MAJOR = 6, + FW_VERSION_MINOR = 7, + FW_VERSION_REVISION = 8, + FW_VERSION_UNRELEASED = 9, + USER_CONFIG_LOADED = 10, + BRAKE_RESISTOR_ARMED = 11, + SYSTEM_STATS__UPTIME = 12, + SYSTEM_STATS__MIN_HEAP_SPACE = 13, + SYSTEM_STATS__MIN_STACK_SPACE_AXIS0 = 14, + SYSTEM_STATS__MIN_STACK_SPACE_AXIS1 = 15, + SYSTEM_STATS__MIN_STACK_SPACE_COMMS = 16, + SYSTEM_STATS__MIN_STACK_SPACE_USB = 17, + SYSTEM_STATS__MIN_STACK_SPACE_UART = 18, + SYSTEM_STATS__MIN_STACK_SPACE_USB_IRQ = 19, + SYSTEM_STATS__MIN_STACK_SPACE_STARTUP = 20, + SYSTEM_STATS__USB__RX_CNT = 21, + SYSTEM_STATS__USB__TX_CNT = 22, + SYSTEM_STATS__USB__TX_OVERRUN_CNT = 23, + SYSTEM_STATS__I2C__ADDR = 24, + SYSTEM_STATS__I2C__ADDR_MATCH_CNT = 25, + SYSTEM_STATS__I2C__RX_CNT = 26, + SYSTEM_STATS__I2C__ERROR_CNT = 27, + CONFIG__BRAKE_RESISTANCE = 28, + CONFIG__ENABLE_UART = 29, + CONFIG__ENABLE_I2C_INSTEAD_OF_CAN = 30, + CONFIG__DC_BUS_UNDERVOLTAGE_TRIP_LEVEL = 31, + CONFIG__DC_BUS_OVERVOLTAGE_TRIP_LEVEL = 32, + AXIS0__ERROR = 33, + AXIS0__ENABLE_STEP_DIR = 34, + AXIS0__CURRENT_STATE = 35, + AXIS0__REQUESTED_STATE = 36, + AXIS0__LOOP_COUNTER = 37, + AXIS0__CONFIG__STARTUP_MOTOR_CALIBRATION = 38, + AXIS0__CONFIG__STARTUP_ENCODER_INDEX_SEARCH = 39, + AXIS0__CONFIG__STARTUP_ENCODER_OFFSET_CALIBRATION = 40, + AXIS0__CONFIG__STARTUP_CLOSED_LOOP_CONTROL = 41, + AXIS0__CONFIG__STARTUP_SENSORLESS_CONTROL = 42, + AXIS0__CONFIG__ENABLE_STEP_DIR = 43, + AXIS0__CONFIG__COUNTS_PER_STEP = 44, + AXIS0__CONFIG__RAMP_UP_TIME = 45, + AXIS0__CONFIG__RAMP_UP_DISTANCE = 46, + AXIS0__CONFIG__SPIN_UP_CURRENT = 47, + AXIS0__CONFIG__SPIN_UP_ACCELERATION = 48, + AXIS0__CONFIG__SPIN_UP_TARGET_VEL = 49, + AXIS0__MOTOR__ERROR = 50, + AXIS0__MOTOR__ARMED_STATE = 51, + AXIS0__MOTOR__IS_CALIBRATED = 52, + AXIS0__MOTOR__CURRENT_MEAS_PHB = 53, + AXIS0__MOTOR__CURRENT_MEAS_PHC = 54, + AXIS0__MOTOR__DC_CALIB_PHB = 55, + AXIS0__MOTOR__DC_CALIB_PHC = 56, + AXIS0__MOTOR__PHASE_CURRENT_REV_GAIN = 57, + AXIS0__MOTOR__CURRENT_CONTROL__P_GAIN = 58, + AXIS0__MOTOR__CURRENT_CONTROL__I_GAIN = 59, + AXIS0__MOTOR__CURRENT_CONTROL__V_CURRENT_CONTROL_INTEGRAL_D = 60, + AXIS0__MOTOR__CURRENT_CONTROL__V_CURRENT_CONTROL_INTEGRAL_Q = 61, + AXIS0__MOTOR__CURRENT_CONTROL__IBUS = 62, + AXIS0__MOTOR__CURRENT_CONTROL__FINAL_V_ALPHA = 63, + AXIS0__MOTOR__CURRENT_CONTROL__FINAL_V_BETA = 64, + AXIS0__MOTOR__CURRENT_CONTROL__IQ_SETPOINT = 65, + AXIS0__MOTOR__CURRENT_CONTROL__IQ_MEASURED = 66, + AXIS0__MOTOR__CURRENT_CONTROL__MAX_ALLOWED_CURRENT = 67, + AXIS0__MOTOR__GATE_DRIVER__DRV_FAULT = 68, + AXIS0__MOTOR__TIMING_LOG__TIMING_LOG_GENERAL = 69, + AXIS0__MOTOR__TIMING_LOG__TIMING_LOG_ADC_CB_I = 70, + AXIS0__MOTOR__TIMING_LOG__TIMING_LOG_ADC_CB_DC = 71, + AXIS0__MOTOR__TIMING_LOG__TIMING_LOG_MEAS_R = 72, + AXIS0__MOTOR__TIMING_LOG__TIMING_LOG_MEAS_L = 73, + AXIS0__MOTOR__TIMING_LOG__TIMING_LOG_ENC_CALIB = 74, + AXIS0__MOTOR__TIMING_LOG__TIMING_LOG_IDX_SEARCH = 75, + AXIS0__MOTOR__TIMING_LOG__TIMING_LOG_FOC_VOLTAGE = 76, + AXIS0__MOTOR__TIMING_LOG__TIMING_LOG_FOC_CURRENT = 77, + AXIS0__MOTOR__CONFIG__PRE_CALIBRATED = 78, + AXIS0__MOTOR__CONFIG__POLE_PAIRS = 79, + AXIS0__MOTOR__CONFIG__CALIBRATION_CURRENT = 80, + AXIS0__MOTOR__CONFIG__RESISTANCE_CALIB_MAX_VOLTAGE = 81, + AXIS0__MOTOR__CONFIG__PHASE_INDUCTANCE = 82, + AXIS0__MOTOR__CONFIG__PHASE_RESISTANCE = 83, + AXIS0__MOTOR__CONFIG__DIRECTION = 84, + AXIS0__MOTOR__CONFIG__MOTOR_TYPE = 85, + AXIS0__MOTOR__CONFIG__CURRENT_LIM = 86, + AXIS0__CONTROLLER__POS_SETPOINT = 87, + AXIS0__CONTROLLER__VEL_SETPOINT = 88, + AXIS0__CONTROLLER__VEL_INTEGRATOR_CURRENT = 89, + AXIS0__CONTROLLER__CURRENT_SETPOINT = 90, + AXIS0__CONTROLLER__CONFIG__CONTROL_MODE = 91, + AXIS0__CONTROLLER__CONFIG__POS_GAIN = 92, + AXIS0__CONTROLLER__CONFIG__VEL_GAIN = 93, + AXIS0__CONTROLLER__CONFIG__VEL_INTEGRATOR_GAIN = 94, + AXIS0__CONTROLLER__CONFIG__VEL_LIMIT = 95, + AXIS0__ENCODER__ERROR = 106, + AXIS0__ENCODER__IS_READY = 107, + AXIS0__ENCODER__INDEX_FOUND = 108, + AXIS0__ENCODER__SHADOW_COUNT = 109, + AXIS0__ENCODER__COUNT_IN_CPR = 110, + AXIS0__ENCODER__OFFSET = 111, + AXIS0__ENCODER__PHASE = 112, + AXIS0__ENCODER__POS_ESTIMATE = 113, + AXIS0__ENCODER__POS_CPR = 114, + AXIS0__ENCODER__PLL_VEL = 115, + AXIS0__ENCODER__PLL_KP = 116, + AXIS0__ENCODER__PLL_KI = 117, + AXIS0__ENCODER__CONFIG__USE_INDEX = 118, + AXIS0__ENCODER__CONFIG__PRE_CALIBRATED = 119, + AXIS0__ENCODER__CONFIG__IDX_SEARCH_SPEED = 120, + AXIS0__ENCODER__CONFIG__CPR = 121, + AXIS0__ENCODER__CONFIG__OFFSET = 122, + AXIS0__ENCODER__CONFIG__CALIB_RANGE = 123, + AXIS0__SENSORLESS_ESTIMATOR__ERROR = 124, + AXIS0__SENSORLESS_ESTIMATOR__PHASE = 125, + AXIS0__SENSORLESS_ESTIMATOR__PLL_POS = 126, + AXIS0__SENSORLESS_ESTIMATOR__PLL_VEL = 127, + AXIS0__SENSORLESS_ESTIMATOR__PLL_KP = 128, + AXIS0__SENSORLESS_ESTIMATOR__PLL_KI = 129, + AXIS1__ERROR = 130, + AXIS1__ENABLE_STEP_DIR = 131, + AXIS1__CURRENT_STATE = 132, + AXIS1__REQUESTED_STATE = 133, + AXIS1__LOOP_COUNTER = 134, + AXIS1__CONFIG__STARTUP_MOTOR_CALIBRATION = 135, + AXIS1__CONFIG__STARTUP_ENCODER_INDEX_SEARCH = 136, + AXIS1__CONFIG__STARTUP_ENCODER_OFFSET_CALIBRATION = 137, + AXIS1__CONFIG__STARTUP_CLOSED_LOOP_CONTROL = 138, + AXIS1__CONFIG__STARTUP_SENSORLESS_CONTROL = 139, + AXIS1__CONFIG__ENABLE_STEP_DIR = 140, + AXIS1__CONFIG__COUNTS_PER_STEP = 141, + AXIS1__CONFIG__RAMP_UP_TIME = 142, + AXIS1__CONFIG__RAMP_UP_DISTANCE = 143, + AXIS1__CONFIG__SPIN_UP_CURRENT = 144, + AXIS1__CONFIG__SPIN_UP_ACCELERATION = 145, + AXIS1__CONFIG__SPIN_UP_TARGET_VEL = 146, + AXIS1__MOTOR__ERROR = 147, + AXIS1__MOTOR__ARMED_STATE = 148, + AXIS1__MOTOR__IS_CALIBRATED = 149, + AXIS1__MOTOR__CURRENT_MEAS_PHB = 150, + AXIS1__MOTOR__CURRENT_MEAS_PHC = 151, + AXIS1__MOTOR__DC_CALIB_PHB = 152, + AXIS1__MOTOR__DC_CALIB_PHC = 153, + AXIS1__MOTOR__PHASE_CURRENT_REV_GAIN = 154, + AXIS1__MOTOR__CURRENT_CONTROL__P_GAIN = 155, + AXIS1__MOTOR__CURRENT_CONTROL__I_GAIN = 156, + AXIS1__MOTOR__CURRENT_CONTROL__V_CURRENT_CONTROL_INTEGRAL_D = 157, + AXIS1__MOTOR__CURRENT_CONTROL__V_CURRENT_CONTROL_INTEGRAL_Q = 158, + AXIS1__MOTOR__CURRENT_CONTROL__IBUS = 159, + AXIS1__MOTOR__CURRENT_CONTROL__FINAL_V_ALPHA = 160, + AXIS1__MOTOR__CURRENT_CONTROL__FINAL_V_BETA = 161, + AXIS1__MOTOR__CURRENT_CONTROL__IQ_SETPOINT = 162, + AXIS1__MOTOR__CURRENT_CONTROL__IQ_MEASURED = 163, + AXIS1__MOTOR__CURRENT_CONTROL__MAX_ALLOWED_CURRENT = 164, + AXIS1__MOTOR__GATE_DRIVER__DRV_FAULT = 165, + AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_GENERAL = 166, + AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_ADC_CB_I = 167, + AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_ADC_CB_DC = 168, + AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_MEAS_R = 169, + AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_MEAS_L = 170, + AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_ENC_CALIB = 171, + AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_IDX_SEARCH = 172, + AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_FOC_VOLTAGE = 173, + AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_FOC_CURRENT = 174, + AXIS1__MOTOR__CONFIG__PRE_CALIBRATED = 175, + AXIS1__MOTOR__CONFIG__POLE_PAIRS = 176, + AXIS1__MOTOR__CONFIG__CALIBRATION_CURRENT = 177, + AXIS1__MOTOR__CONFIG__RESISTANCE_CALIB_MAX_VOLTAGE = 178, + AXIS1__MOTOR__CONFIG__PHASE_INDUCTANCE = 179, + AXIS1__MOTOR__CONFIG__PHASE_RESISTANCE = 180, + AXIS1__MOTOR__CONFIG__DIRECTION = 181, + AXIS1__MOTOR__CONFIG__MOTOR_TYPE = 182, + AXIS1__MOTOR__CONFIG__CURRENT_LIM = 183, + AXIS1__CONTROLLER__POS_SETPOINT = 184, + AXIS1__CONTROLLER__VEL_SETPOINT = 185, + AXIS1__CONTROLLER__VEL_INTEGRATOR_CURRENT = 186, + AXIS1__CONTROLLER__CURRENT_SETPOINT = 187, + AXIS1__CONTROLLER__CONFIG__CONTROL_MODE = 188, + AXIS1__CONTROLLER__CONFIG__POS_GAIN = 189, + AXIS1__CONTROLLER__CONFIG__VEL_GAIN = 190, + AXIS1__CONTROLLER__CONFIG__VEL_INTEGRATOR_GAIN = 191, + AXIS1__CONTROLLER__CONFIG__VEL_LIMIT = 192, + AXIS1__ENCODER__ERROR = 203, + AXIS1__ENCODER__IS_READY = 204, + AXIS1__ENCODER__INDEX_FOUND = 205, + AXIS1__ENCODER__SHADOW_COUNT = 206, + AXIS1__ENCODER__COUNT_IN_CPR = 207, + AXIS1__ENCODER__OFFSET = 208, + AXIS1__ENCODER__PHASE = 209, + AXIS1__ENCODER__POS_ESTIMATE = 210, + AXIS1__ENCODER__POS_CPR = 211, + AXIS1__ENCODER__PLL_VEL = 212, + AXIS1__ENCODER__PLL_KP = 213, + AXIS1__ENCODER__PLL_KI = 214, + AXIS1__ENCODER__CONFIG__USE_INDEX = 215, + AXIS1__ENCODER__CONFIG__PRE_CALIBRATED = 216, + AXIS1__ENCODER__CONFIG__IDX_SEARCH_SPEED = 217, + AXIS1__ENCODER__CONFIG__CPR = 218, + AXIS1__ENCODER__CONFIG__OFFSET = 219, + AXIS1__ENCODER__CONFIG__CALIB_RANGE = 220, + AXIS1__SENSORLESS_ESTIMATOR__ERROR = 221, + AXIS1__SENSORLESS_ESTIMATOR__PHASE = 222, + AXIS1__SENSORLESS_ESTIMATOR__PLL_POS = 223, + AXIS1__SENSORLESS_ESTIMATOR__PLL_VEL = 224, + AXIS1__SENSORLESS_ESTIMATOR__PLL_KP = 225, + AXIS1__SENSORLESS_ESTIMATOR__PLL_KI = 226, + TEST_PROPERTY = 227, +}; + +template +struct endpoint_type; + +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint64_t type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint32_t type; }; + + +template +using endpoint_type_t = typename endpoint_type::type; + +} + +#endif __ODRIVE_ENDPOINTS_HPP \ No newline at end of file diff --git a/ArduinoI2C/type_traits.h b/ArduinoI2C/type_traits.h new file mode 100644 index 00000000..4c4b0367 --- /dev/null +++ b/ArduinoI2C/type_traits.h @@ -0,0 +1,267 @@ +/* +* This file is a very small part of the GCC STL because AVR-GCC ships +* without the STL. +*/ + +namespace std +{ + + /** + * @defgroup metaprogramming Metaprogramming + * @ingroup utilities + * + * Template utilities for compile-time introspection and modification, + * including type classification traits, type property inspection traits + * and type transformation traits. + * + * @{ + */ + + /// integral_constant + template + struct integral_constant + { + static constexpr _Tp value = __v; + typedef _Tp value_type; + typedef integral_constant<_Tp, __v> type; + constexpr operator value_type() const noexcept { return value; } +#if __cplusplus > 201103L + +#define __cpp_lib_integral_constant_callable 201304 + + constexpr value_type operator()() const noexcept { return value; } +#endif + }; + + template + constexpr _Tp integral_constant<_Tp, __v>::value; + + /// The type used as a compile-time boolean with true value. + typedef integral_constant true_type; + + /// The type used as a compile-time boolean with false value. + typedef integral_constant false_type; + + template + using __bool_constant = integral_constant; + +#if __cplusplus > 201402L +# define __cpp_lib_bool_constant 201505 + template + using bool_constant = integral_constant; +#endif + + + // Primary type categories. + + template + struct remove_cv; + + template + struct __is_void_helper + : public false_type { }; + + template<> + struct __is_void_helper + : public true_type { }; + + /// is_void + template + struct is_void + : public __is_void_helper::type>::type + { }; + + template + struct __is_integral_helper + : public false_type { }; + + template<> + struct __is_integral_helper + : public true_type { }; + + template<> + struct __is_integral_helper + : public true_type { }; + + template<> + struct __is_integral_helper + : public true_type { }; + + template<> + struct __is_integral_helper + : public true_type { }; + +#ifdef _GLIBCXX_USE_WCHAR_T + template<> + struct __is_integral_helper + : public true_type { }; +#endif + + template<> + struct __is_integral_helper + : public true_type { }; + + template<> + struct __is_integral_helper + : public true_type { }; + + template<> + struct __is_integral_helper + : public true_type { }; + + template<> + struct __is_integral_helper + : public true_type { }; + + template<> + struct __is_integral_helper + : public true_type { }; + + template<> + struct __is_integral_helper + : public true_type { }; + + template<> + struct __is_integral_helper + : public true_type { }; + + template<> + struct __is_integral_helper + : public true_type { }; + + template<> + struct __is_integral_helper + : public true_type { }; + + template<> + struct __is_integral_helper + : public true_type { }; + + // Conditionalizing on __STRICT_ANSI__ here will break any port that + // uses one of these types for size_t. +#if defined(__GLIBCXX_TYPE_INT_N_0) + template<> + struct __is_integral_helper<__GLIBCXX_TYPE_INT_N_0> + : public true_type { }; + + template<> + struct __is_integral_helper + : public true_type { }; +#endif +#if defined(__GLIBCXX_TYPE_INT_N_1) + template<> + struct __is_integral_helper<__GLIBCXX_TYPE_INT_N_1> + : public true_type { }; + + template<> + struct __is_integral_helper + : public true_type { }; +#endif +#if defined(__GLIBCXX_TYPE_INT_N_2) + template<> + struct __is_integral_helper<__GLIBCXX_TYPE_INT_N_2> + : public true_type { }; + + template<> + struct __is_integral_helper + : public true_type { }; +#endif +#if defined(__GLIBCXX_TYPE_INT_N_3) + template<> + struct __is_integral_helper<__GLIBCXX_TYPE_INT_N_3> + : public true_type { }; + + template<> + struct __is_integral_helper + : public true_type { }; +#endif + + /// is_integral + template + struct is_integral + : public __is_integral_helper::type>::type + { }; + + template + struct __is_floating_point_helper + : public false_type { }; + + template<> + struct __is_floating_point_helper + : public true_type { }; + + template<> + struct __is_floating_point_helper + : public true_type { }; + + template<> + struct __is_floating_point_helper + : public true_type { }; + +#if !defined(__STRICT_ANSI__) && defined(_GLIBCXX_USE_FLOAT128) + template<> + struct __is_floating_point_helper<__float128> + : public true_type { }; +#endif + + /// is_floating_point + template + struct is_floating_point + : public __is_floating_point_helper::type>::type + { }; + + + + + // Const-volatile modifications. + + /// remove_const + template + struct remove_const + { typedef _Tp type; }; + + template + struct remove_const<_Tp const> + { typedef _Tp type; }; + + /// remove_volatile + template + struct remove_volatile + { typedef _Tp type; }; + + template + struct remove_volatile<_Tp volatile> + { typedef _Tp type; }; + + /// remove_cv + template + struct remove_cv + { + typedef typename + remove_const::type>::type type; + }; + + + // Primary template. + /// Define a member typedef @c type only if a boolean constant is true. + template + struct enable_if + { }; + + // Partial specialization for true. + template + struct enable_if + { typedef _Tp type; }; + + + // Type relations. + + /// is_same + template + struct is_same + : public false_type { }; + + template + struct is_same<_Tp, _Tp> + : public true_type { }; +} From 1817b978b1e902b3e23a1f00125f2975a7355671 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 24 Apr 2018 19:36:07 -0700 Subject: [PATCH 140/215] add test property --- Firmware/communication/communication.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index b6ce55ff..a0234a3f 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -65,6 +65,8 @@ const uint8_t fw_version_unreleased = FW_VERSION_UNRELEASED; // 0 for official r osThreadId comm_thread; +static uint32_t test_property = 0; + /* Private function prototypes -----------------------------------------------*/ /* Function implementations --------------------------------------------------*/ @@ -142,8 +144,9 @@ static inline auto make_obj_tree() { ), make_protocol_object("axis0", axes[0]->make_protocol_definitions()), make_protocol_object("axis1", axes[1]->make_protocol_definitions()), - make_protocol_function("get_oscilloscope_val", static_functions, &StaticFunctions::get_oscilloscope_val, "index"), + make_protocol_property("test_property", &test_property), make_protocol_function("test_function", static_functions, &StaticFunctions::test_function, "delta"), + make_protocol_function("get_oscilloscope_val", static_functions, &StaticFunctions::get_oscilloscope_val, "index"), make_protocol_function("save_configuration", static_functions, &StaticFunctions::save_configuration_helper), make_protocol_function("erase_configuration", static_functions, &StaticFunctions::erase_configuration_helper), make_protocol_function("reboot", static_functions, &StaticFunctions::NVIC_SystemReset_helper), From af502d349beb3457cbd55259c0b01b668fc34d39 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 24 Apr 2018 19:37:09 -0700 Subject: [PATCH 141/215] continue operation after temporary I2C bus errors --- Firmware/MotorControl/main.cpp | 2 +- Firmware/communication/interface_i2c.cpp | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 003d82ad..2d744d5b 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -109,7 +109,7 @@ int odrive_main(void) { // Set up the direction GPIO as input GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; - GPIO_InitStruct.Pull = GPIO_NOPULL; + GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Pin = I2C_A0_PIN; HAL_GPIO_Init(I2C_A0_PORT, &GPIO_InitStruct); diff --git a/Firmware/communication/interface_i2c.cpp b/Firmware/communication/interface_i2c.cpp index 525b4be0..7138f662 100644 --- a/Firmware/communication/interface_i2c.cpp +++ b/Firmware/communication/interface_i2c.cpp @@ -75,6 +75,12 @@ void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, ui } void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c) { - if (hi2c->ErrorCode & (~HAL_I2C_ERROR_AF)) // ignore NACK errors - i2c_stats_.error_cnt += 1; + // ignore NACK errors + if (!(hi2c->ErrorCode & (~HAL_I2C_ERROR_AF))) + return; + + i2c_stats_.error_cnt += 1; + + // Continue listening + HAL_I2C_EnableListen_IT(hi2c); } From a067d31bb956a3cf63e8db6aefd0e7014520821f Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 24 Apr 2018 19:38:09 -0700 Subject: [PATCH 142/215] rename template_processor to code_generator --- ...{template_processor.py => code_generator.py} | 2 +- tools/odrive_header_template.h.in | 2 +- tools/odrivetool | 17 +++++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) rename tools/odrive/{template_processor.py => code_generator.py} (96%) diff --git a/tools/odrive/template_processor.py b/tools/odrive/code_generator.py similarity index 96% rename from tools/odrive/template_processor.py rename to tools/odrive/code_generator.py index 757b6d6a..a3c9c096 100644 --- a/tools/odrive/template_processor.py +++ b/tools/odrive/code_generator.py @@ -22,7 +22,7 @@ def get_flat_endpoint_list(json, prefix): flat_list = flat_list + get_flat_endpoint_list(item['members'], prefix + item['name'] + '.') return flat_list -def generate_header(odrv, template_file, output_file): +def generate_code(odrv, template_file, output_file): json_data = odrv._json_data json_crc = odrv._json_crc diff --git a/tools/odrive_header_template.h.in b/tools/odrive_header_template.h.in index 392cddd6..05e96376 100644 --- a/tools/odrive_header_template.h.in +++ b/tools/odrive_header_template.h.in @@ -1,5 +1,5 @@ /* -* This file was autogenerated using the "odrivetool process-template" feature. +* This file was autogenerated using the "odrivetool generate-code" feature. * * The file matches a specific firmware version. If you add/remove/rename any * properties exposed by the ODrive, this file needs to be regenerated, otherwise diff --git a/tools/odrivetool b/tools/odrivetool index eba8e829..1644acab 100755 --- a/tools/odrivetool +++ b/tools/odrivetool @@ -38,12 +38,12 @@ shell_parser.add_argument("--no-ipython", action="store_true", dfu_parser = subparsers.add_parser('dfu', help="Upgrade the ODrive device firmware") dfu_parser.add_argument('file', metavar='HEX', help='The .hex file to be flashed. Make sure your firmware board version matches the actual board version.') -template_processor_parser = subparsers.add_parser('process-template', help="Process a jinja2 template, passing the ODrive's JSON data as data input") -template_processor_parser.add_argument("-t", "--template", type=argparse.FileType('r'), +code_generator_parser = subparsers.add_parser('generate-code', help="Process a jinja2 template, passing the ODrive's JSON data as data input") +code_generator_parser.add_argument("-t", "--template", type=argparse.FileType('r'), help="the code template") -template_processor_parser.add_argument("-o", "--output", type=argparse.FileType('w'), default='-', +code_generator_parser.add_argument("-o", "--output", type=argparse.FileType('w'), default='-', help="path of the generated output") -template_processor_parser.set_defaults(template = os.path.join(script_path, 'odrive_header_template.h.in')) +code_generator_parser.set_defaults(template = os.path.join(script_path, 'odrive_header_template.h.in')) subparsers.add_parser('liveplotter', help="Upgrade the ODrive's Firmware") subparsers.add_parser('drv-status', help="Show status of the on-board DRV8301 chips (for debugging only)") @@ -148,10 +148,11 @@ try: from odrive.utils import setup_udev_rules setup_udev_rules(logger) - elif args.command == 'process-template': - from odrive.template_processor import generate_header - my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number) - generate_header(my_odrive, args.template, args.output) + elif args.command == 'generate-code': + from odrive.code_generator import generate_code + my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number, + channel_termination_token=app_shutdown_token) + generate_code(my_odrive, args.template, args.output) else: raise Exception("unknown command: " + args.command) From e19c477875335a50b3c5a0b37e14a525726bd5b3 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 24 Apr 2018 19:36:07 -0700 Subject: [PATCH 143/215] add test property --- Firmware/communication/communication.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index ae611f24..1c4accfd 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -64,6 +64,8 @@ const uint8_t fw_version_unreleased = FW_VERSION_UNRELEASED; // 0 for official r osThreadId comm_thread; +static uint32_t test_property = 0; + /* Private function prototypes -----------------------------------------------*/ /* Function implementations --------------------------------------------------*/ @@ -134,8 +136,9 @@ static inline auto make_obj_tree() { ), make_protocol_object("axis0", axes[0]->make_protocol_definitions()), make_protocol_object("axis1", axes[1]->make_protocol_definitions()), - make_protocol_function("get_oscilloscope_val", static_functions, &StaticFunctions::get_oscilloscope_val, "index"), + make_protocol_property("test_property", &test_property), make_protocol_function("test_function", static_functions, &StaticFunctions::test_function, "delta"), + make_protocol_function("get_oscilloscope_val", static_functions, &StaticFunctions::get_oscilloscope_val, "index"), make_protocol_function("save_configuration", static_functions, &StaticFunctions::save_configuration_helper), make_protocol_function("erase_configuration", static_functions, &StaticFunctions::erase_configuration_helper), make_protocol_function("reboot", static_functions, &StaticFunctions::NVIC_SystemReset_helper), From 50965e0c8487bdf1b1ed9813506f5f34a5ea4e56 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 26 Apr 2018 13:22:24 -0700 Subject: [PATCH 144/215] add I2C patch file --- .../v3/0002-Add-I2C-files-and-settings.patch | 6923 +++++++++++++++++ 1 file changed, 6923 insertions(+) create mode 100644 Firmware/Board/v3/0002-Add-I2C-files-and-settings.patch diff --git a/Firmware/Board/v3/0002-Add-I2C-files-and-settings.patch b/Firmware/Board/v3/0002-Add-I2C-files-and-settings.patch new file mode 100644 index 00000000..e1b7e714 --- /dev/null +++ b/Firmware/Board/v3/0002-Add-I2C-files-and-settings.patch @@ -0,0 +1,6923 @@ +From b45bdbbbfe3d084d069da99b98116991923f5eb6 Mon Sep 17 00:00:00 2001 +From: Samuel Sadok +Date: Thu, 26 Apr 2018 13:20:38 -0700 +Subject: [PATCH] Add I2C files and settings + +--- + .../Inc/stm32f4xx_hal_i2c.h | 649 ++ + .../Inc/stm32f4xx_hal_i2c_ex.h | 137 + + .../Src/stm32f4xx_hal_i2c.c | 5494 +++++++++++++++++ + .../Src/stm32f4xx_hal_i2c_ex.c | 204 + + Firmware/Board/v3/Inc/i2c.h | 91 + + Firmware/Board/v3/Inc/stm32f4xx_hal_conf.h | 2 +- + Firmware/Board/v3/Inc/stm32f4xx_it.h | 1 + + Firmware/Board/v3/Odrive.ioc | 5 + + Firmware/Board/v3/Src/i2c.c | 198 + + Firmware/Board/v3/Src/main.c | 1 - + Firmware/Board/v3/Src/stm32f4xx_it.c | 31 + + 11 files changed, 6811 insertions(+), 2 deletions(-) + create mode 100644 Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c.h + create mode 100644 Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c_ex.h + create mode 100644 Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c.c + create mode 100644 Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c_ex.c + create mode 100644 Firmware/Board/v3/Inc/i2c.h + create mode 100644 Firmware/Board/v3/Src/i2c.c + +diff --git a/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c.h b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c.h +new file mode 100644 +index 0000000..5452a50 +--- /dev/null ++++ b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c.h +@@ -0,0 +1,649 @@ ++/** ++ ****************************************************************************** ++ * @file stm32f4xx_hal_i2c.h ++ * @author MCD Application Team ++ * @brief Header file of I2C HAL module. ++ ****************************************************************************** ++ * @attention ++ * ++ *

© COPYRIGHT(c) 2017 STMicroelectronics

++ * ++ * Redistribution and use in source and binary forms, with or without modification, ++ * are permitted provided that the following conditions are met: ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright notice, ++ * this list of conditions and the following disclaimer in the documentation ++ * and/or other materials provided with the distribution. ++ * 3. Neither the name of STMicroelectronics nor the names of its contributors ++ * may be used to endorse or promote products derived from this software ++ * without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE ++ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ++ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR ++ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER ++ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ++ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ * ++ ****************************************************************************** ++ */ ++ ++/* Define to prevent recursive inclusion -------------------------------------*/ ++#ifndef __STM32F4xx_HAL_I2C_H ++#define __STM32F4xx_HAL_I2C_H ++ ++#ifdef __cplusplus ++ extern "C" { ++#endif ++ ++/* Includes ------------------------------------------------------------------*/ ++#include "stm32f4xx_hal_def.h" ++ ++/** @addtogroup STM32F4xx_HAL_Driver ++ * @{ ++ */ ++ ++/** @addtogroup I2C ++ * @{ ++ */ ++ ++/* Exported types ------------------------------------------------------------*/ ++/** @defgroup I2C_Exported_Types I2C Exported Types ++ * @{ ++ */ ++ ++/** ++ * @brief I2C Configuration Structure definition ++ */ ++typedef struct ++{ ++ uint32_t ClockSpeed; /*!< Specifies the clock frequency. ++ This parameter must be set to a value lower than 400kHz */ ++ ++ uint32_t DutyCycle; /*!< Specifies the I2C fast mode duty cycle. ++ This parameter can be a value of @ref I2C_duty_cycle_in_fast_mode */ ++ ++ uint32_t OwnAddress1; /*!< Specifies the first device own address. ++ This parameter can be a 7-bit or 10-bit address. */ ++ ++ uint32_t AddressingMode; /*!< Specifies if 7-bit or 10-bit addressing mode is selected. ++ This parameter can be a value of @ref I2C_addressing_mode */ ++ ++ uint32_t DualAddressMode; /*!< Specifies if dual addressing mode is selected. ++ This parameter can be a value of @ref I2C_dual_addressing_mode */ ++ ++ uint32_t OwnAddress2; /*!< Specifies the second device own address if dual addressing mode is selected ++ This parameter can be a 7-bit address. */ ++ ++ uint32_t GeneralCallMode; /*!< Specifies if general call mode is selected. ++ This parameter can be a value of @ref I2C_general_call_addressing_mode */ ++ ++ uint32_t NoStretchMode; /*!< Specifies if nostretch mode is selected. ++ This parameter can be a value of @ref I2C_nostretch_mode */ ++ ++}I2C_InitTypeDef; ++ ++/** ++ * @brief HAL State structure definition ++ * @note HAL I2C State value coding follow below described bitmap : ++ * b7-b6 Error information ++ * 00 : No Error ++ * 01 : Abort (Abort user request on going) ++ * 10 : Timeout ++ * 11 : Error ++ * b5 IP initilisation status ++ * 0 : Reset (IP not initialized) ++ * 1 : Init done (IP initialized and ready to use. HAL I2C Init function called) ++ * b4 (not used) ++ * x : Should be set to 0 ++ * b3 ++ * 0 : Ready or Busy (No Listen mode ongoing) ++ * 1 : Listen (IP in Address Listen Mode) ++ * b2 Intrinsic process state ++ * 0 : Ready ++ * 1 : Busy (IP busy with some configuration or internal operations) ++ * b1 Rx state ++ * 0 : Ready (no Rx operation ongoing) ++ * 1 : Busy (Rx operation ongoing) ++ * b0 Tx state ++ * 0 : Ready (no Tx operation ongoing) ++ * 1 : Busy (Tx operation ongoing) ++ */ ++typedef enum ++{ ++ HAL_I2C_STATE_RESET = 0x00U, /*!< Peripheral is not yet Initialized */ ++ HAL_I2C_STATE_READY = 0x20U, /*!< Peripheral Initialized and ready for use */ ++ HAL_I2C_STATE_BUSY = 0x24U, /*!< An internal process is ongoing */ ++ HAL_I2C_STATE_BUSY_TX = 0x21U, /*!< Data Transmission process is ongoing */ ++ HAL_I2C_STATE_BUSY_RX = 0x22U, /*!< Data Reception process is ongoing */ ++ HAL_I2C_STATE_LISTEN = 0x28U, /*!< Address Listen Mode is ongoing */ ++ HAL_I2C_STATE_BUSY_TX_LISTEN = 0x29U, /*!< Address Listen Mode and Data Transmission ++ process is ongoing */ ++ HAL_I2C_STATE_BUSY_RX_LISTEN = 0x2AU, /*!< Address Listen Mode and Data Reception ++ process is ongoing */ ++ HAL_I2C_STATE_ABORT = 0x60U, /*!< Abort user request ongoing */ ++ HAL_I2C_STATE_TIMEOUT = 0xA0U, /*!< Timeout state */ ++ HAL_I2C_STATE_ERROR = 0xE0U /*!< Error */ ++ ++}HAL_I2C_StateTypeDef; ++ ++/** ++ * @brief HAL Mode structure definition ++ * @note HAL I2C Mode value coding follow below described bitmap : ++ * b7 (not used) ++ * x : Should be set to 0 ++ * b6 ++ * 0 : None ++ * 1 : Memory (HAL I2C communication is in Memory Mode) ++ * b5 ++ * 0 : None ++ * 1 : Slave (HAL I2C communication is in Slave Mode) ++ * b4 ++ * 0 : None ++ * 1 : Master (HAL I2C communication is in Master Mode) ++ * b3-b2-b1-b0 (not used) ++ * xxxx : Should be set to 0000 ++ */ ++typedef enum ++{ ++ HAL_I2C_MODE_NONE = 0x00U, /*!< No I2C communication on going */ ++ HAL_I2C_MODE_MASTER = 0x10U, /*!< I2C communication is in Master Mode */ ++ HAL_I2C_MODE_SLAVE = 0x20U, /*!< I2C communication is in Slave Mode */ ++ HAL_I2C_MODE_MEM = 0x40U /*!< I2C communication is in Memory Mode */ ++ ++}HAL_I2C_ModeTypeDef; ++ ++/** ++ * @brief I2C handle Structure definition ++ */ ++typedef struct ++{ ++ I2C_TypeDef *Instance; /*!< I2C registers base address */ ++ ++ I2C_InitTypeDef Init; /*!< I2C communication parameters */ ++ ++ uint8_t *pBuffPtr; /*!< Pointer to I2C transfer buffer */ ++ ++ uint16_t XferSize; /*!< I2C transfer size */ ++ ++ __IO uint16_t XferCount; /*!< I2C transfer counter */ ++ ++ __IO uint32_t XferOptions; /*!< I2C transfer options */ ++ ++ __IO uint32_t PreviousState; /*!< I2C communication Previous state and mode ++ context for internal usage */ ++ ++ DMA_HandleTypeDef *hdmatx; /*!< I2C Tx DMA handle parameters */ ++ ++ DMA_HandleTypeDef *hdmarx; /*!< I2C Rx DMA handle parameters */ ++ ++ HAL_LockTypeDef Lock; /*!< I2C locking object */ ++ ++ __IO HAL_I2C_StateTypeDef State; /*!< I2C communication state */ ++ ++ __IO HAL_I2C_ModeTypeDef Mode; /*!< I2C communication mode */ ++ ++ __IO uint32_t ErrorCode; /*!< I2C Error code */ ++ ++ __IO uint32_t Devaddress; /*!< I2C Target device address */ ++ ++ __IO uint32_t Memaddress; /*!< I2C Target memory address */ ++ ++ __IO uint32_t MemaddSize; /*!< I2C Target memory address size */ ++ ++ __IO uint32_t EventCount; /*!< I2C Event counter */ ++ ++}I2C_HandleTypeDef; ++ ++/** ++ * @} ++ */ ++ ++/* Exported constants --------------------------------------------------------*/ ++/** @defgroup I2C_Exported_Constants I2C Exported Constants ++ * @{ ++ */ ++ ++/** @defgroup I2C_Error_Code I2C Error Code ++ * @brief I2C Error Code ++ * @{ ++ */ ++#define HAL_I2C_ERROR_NONE 0x00000000U /*!< No error */ ++#define HAL_I2C_ERROR_BERR 0x00000001U /*!< BERR error */ ++#define HAL_I2C_ERROR_ARLO 0x00000002U /*!< ARLO error */ ++#define HAL_I2C_ERROR_AF 0x00000004U /*!< AF error */ ++#define HAL_I2C_ERROR_OVR 0x00000008U /*!< OVR error */ ++#define HAL_I2C_ERROR_DMA 0x00000010U /*!< DMA transfer error */ ++#define HAL_I2C_ERROR_TIMEOUT 0x00000020U /*!< Timeout Error */ ++/** ++ * @} ++ */ ++ ++/** @defgroup I2C_duty_cycle_in_fast_mode I2C duty cycle in fast mode ++ * @{ ++ */ ++#define I2C_DUTYCYCLE_2 0x00000000U ++#define I2C_DUTYCYCLE_16_9 I2C_CCR_DUTY ++/** ++ * @} ++ */ ++ ++/** @defgroup I2C_addressing_mode I2C addressing mode ++ * @{ ++ */ ++#define I2C_ADDRESSINGMODE_7BIT 0x00004000U ++#define I2C_ADDRESSINGMODE_10BIT (I2C_OAR1_ADDMODE | 0x00004000U) ++/** ++ * @} ++ */ ++ ++/** @defgroup I2C_dual_addressing_mode I2C dual addressing mode ++ * @{ ++ */ ++#define I2C_DUALADDRESS_DISABLE 0x00000000U ++#define I2C_DUALADDRESS_ENABLE I2C_OAR2_ENDUAL ++/** ++ * @} ++ */ ++ ++/** @defgroup I2C_general_call_addressing_mode I2C general call addressing mode ++ * @{ ++ */ ++#define I2C_GENERALCALL_DISABLE 0x00000000U ++#define I2C_GENERALCALL_ENABLE I2C_CR1_ENGC ++/** ++ * @} ++ */ ++ ++/** @defgroup I2C_nostretch_mode I2C nostretch mode ++ * @{ ++ */ ++#define I2C_NOSTRETCH_DISABLE 0x00000000U ++#define I2C_NOSTRETCH_ENABLE I2C_CR1_NOSTRETCH ++/** ++ * @} ++ */ ++ ++/** @defgroup I2C_Memory_Address_Size I2C Memory Address Size ++ * @{ ++ */ ++#define I2C_MEMADD_SIZE_8BIT 0x00000001U ++#define I2C_MEMADD_SIZE_16BIT 0x00000010U ++/** ++ * @} ++ */ ++ ++/** @defgroup I2C_XferDirection_definition I2C XferDirection definition ++ * @{ ++ */ ++#define I2C_DIRECTION_RECEIVE 0x00000000U ++#define I2C_DIRECTION_TRANSMIT 0x00000001U ++/** ++ * @} ++ */ ++ ++/** @defgroup I2C_XferOptions_definition I2C XferOptions definition ++ * @{ ++ */ ++#define I2C_FIRST_FRAME 0x00000001U ++#define I2C_NEXT_FRAME 0x00000002U ++#define I2C_FIRST_AND_LAST_FRAME 0x00000004U ++#define I2C_LAST_FRAME 0x00000008U ++/** ++ * @} ++ */ ++ ++/** @defgroup I2C_Interrupt_configuration_definition I2C Interrupt configuration definition ++ * @{ ++ */ ++#define I2C_IT_BUF I2C_CR2_ITBUFEN ++#define I2C_IT_EVT I2C_CR2_ITEVTEN ++#define I2C_IT_ERR I2C_CR2_ITERREN ++/** ++ * @} ++ */ ++ ++/** @defgroup I2C_Flag_definition I2C Flag definition ++ * @{ ++ */ ++#define I2C_FLAG_SMBALERT 0x00018000U ++#define I2C_FLAG_TIMEOUT 0x00014000U ++#define I2C_FLAG_PECERR 0x00011000U ++#define I2C_FLAG_OVR 0x00010800U ++#define I2C_FLAG_AF 0x00010400U ++#define I2C_FLAG_ARLO 0x00010200U ++#define I2C_FLAG_BERR 0x00010100U ++#define I2C_FLAG_TXE 0x00010080U ++#define I2C_FLAG_RXNE 0x00010040U ++#define I2C_FLAG_STOPF 0x00010010U ++#define I2C_FLAG_ADD10 0x00010008U ++#define I2C_FLAG_BTF 0x00010004U ++#define I2C_FLAG_ADDR 0x00010002U ++#define I2C_FLAG_SB 0x00010001U ++#define I2C_FLAG_DUALF 0x00100080U ++#define I2C_FLAG_SMBHOST 0x00100040U ++#define I2C_FLAG_SMBDEFAULT 0x00100020U ++#define I2C_FLAG_GENCALL 0x00100010U ++#define I2C_FLAG_TRA 0x00100004U ++#define I2C_FLAG_BUSY 0x00100002U ++#define I2C_FLAG_MSL 0x00100001U ++/** ++ * @} ++ */ ++ ++/** ++ * @} ++ */ ++ ++/* Exported macro ------------------------------------------------------------*/ ++/** @defgroup I2C_Exported_Macros I2C Exported Macros ++ * @{ ++ */ ++ ++/** @brief Reset I2C handle state ++ * @param __HANDLE__ specifies the I2C Handle. ++ * This parameter can be I2C where x: 1, 2, or 3 to select the I2C peripheral. ++ * @retval None ++ */ ++#define __HAL_I2C_RESET_HANDLE_STATE(__HANDLE__) ((__HANDLE__)->State = HAL_I2C_STATE_RESET) ++ ++/** @brief Enable or disable the specified I2C interrupts. ++ * @param __HANDLE__ specifies the I2C Handle. ++ * This parameter can be I2C where x: 1, 2, or 3 to select the I2C peripheral. ++ * @param __INTERRUPT__ specifies the interrupt source to enable or disable. ++ * This parameter can be one of the following values: ++ * @arg I2C_IT_BUF: Buffer interrupt enable ++ * @arg I2C_IT_EVT: Event interrupt enable ++ * @arg I2C_IT_ERR: Error interrupt enable ++ * @retval None ++ */ ++#define __HAL_I2C_ENABLE_IT(__HANDLE__, __INTERRUPT__) ((__HANDLE__)->Instance->CR2 |= (__INTERRUPT__)) ++#define __HAL_I2C_DISABLE_IT(__HANDLE__, __INTERRUPT__) ((__HANDLE__)->Instance->CR2 &= (~(__INTERRUPT__))) ++ ++/** @brief Checks if the specified I2C interrupt source is enabled or disabled. ++ * @param __HANDLE__ specifies the I2C Handle. ++ * This parameter can be I2C where x: 1, 2, or 3 to select the I2C peripheral. ++ * @param __INTERRUPT__ specifies the I2C interrupt source to check. ++ * This parameter can be one of the following values: ++ * @arg I2C_IT_BUF: Buffer interrupt enable ++ * @arg I2C_IT_EVT: Event interrupt enable ++ * @arg I2C_IT_ERR: Error interrupt enable ++ * @retval The new state of __INTERRUPT__ (TRUE or FALSE). ++ */ ++#define __HAL_I2C_GET_IT_SOURCE(__HANDLE__, __INTERRUPT__) ((((__HANDLE__)->Instance->CR2 & (__INTERRUPT__)) == (__INTERRUPT__)) ? SET : RESET) ++ ++/** @brief Checks whether the specified I2C flag is set or not. ++ * @param __HANDLE__ specifies the I2C Handle. ++ * This parameter can be I2C where x: 1, 2, or 3 to select the I2C peripheral. ++ * @param __FLAG__ specifies the flag to check. ++ * This parameter can be one of the following values: ++ * @arg I2C_FLAG_SMBALERT: SMBus Alert flag ++ * @arg I2C_FLAG_TIMEOUT: Timeout or Tlow error flag ++ * @arg I2C_FLAG_PECERR: PEC error in reception flag ++ * @arg I2C_FLAG_OVR: Overrun/Underrun flag ++ * @arg I2C_FLAG_AF: Acknowledge failure flag ++ * @arg I2C_FLAG_ARLO: Arbitration lost flag ++ * @arg I2C_FLAG_BERR: Bus error flag ++ * @arg I2C_FLAG_TXE: Data register empty flag ++ * @arg I2C_FLAG_RXNE: Data register not empty flag ++ * @arg I2C_FLAG_STOPF: Stop detection flag ++ * @arg I2C_FLAG_ADD10: 10-bit header sent flag ++ * @arg I2C_FLAG_BTF: Byte transfer finished flag ++ * @arg I2C_FLAG_ADDR: Address sent flag ++ * Address matched flag ++ * @arg I2C_FLAG_SB: Start bit flag ++ * @arg I2C_FLAG_DUALF: Dual flag ++ * @arg I2C_FLAG_SMBHOST: SMBus host header ++ * @arg I2C_FLAG_SMBDEFAULT: SMBus default header ++ * @arg I2C_FLAG_GENCALL: General call header flag ++ * @arg I2C_FLAG_TRA: Transmitter/Receiver flag ++ * @arg I2C_FLAG_BUSY: Bus busy flag ++ * @arg I2C_FLAG_MSL: Master/Slave flag ++ * @retval The new state of __FLAG__ (TRUE or FALSE). ++ */ ++#define __HAL_I2C_GET_FLAG(__HANDLE__, __FLAG__) ((((uint8_t)((__FLAG__) >> 16U)) == 0x01U)?((((__HANDLE__)->Instance->SR1) & ((__FLAG__) & I2C_FLAG_MASK)) == ((__FLAG__) & I2C_FLAG_MASK)): \ ++ ((((__HANDLE__)->Instance->SR2) & ((__FLAG__) & I2C_FLAG_MASK)) == ((__FLAG__) & I2C_FLAG_MASK))) ++ ++/** @brief Clears the I2C pending flags which are cleared by writing 0 in a specific bit. ++ * @param __HANDLE__ specifies the I2C Handle. ++ * This parameter can be I2C where x: 1, 2, or 3 to select the I2C peripheral. ++ * @param __FLAG__ specifies the flag to clear. ++ * This parameter can be any combination of the following values: ++ * @arg I2C_FLAG_SMBALERT: SMBus Alert flag ++ * @arg I2C_FLAG_TIMEOUT: Timeout or Tlow error flag ++ * @arg I2C_FLAG_PECERR: PEC error in reception flag ++ * @arg I2C_FLAG_OVR: Overrun/Underrun flag (Slave mode) ++ * @arg I2C_FLAG_AF: Acknowledge failure flag ++ * @arg I2C_FLAG_ARLO: Arbitration lost flag (Master mode) ++ * @arg I2C_FLAG_BERR: Bus error flag ++ * @retval None ++ */ ++#define __HAL_I2C_CLEAR_FLAG(__HANDLE__, __FLAG__) ((__HANDLE__)->Instance->SR1 = ~((__FLAG__) & I2C_FLAG_MASK)) ++ ++/** @brief Clears the I2C ADDR pending flag. ++ * @param __HANDLE__ specifies the I2C Handle. ++ * This parameter can be I2C where x: 1, 2, or 3 to select the I2C peripheral. ++ * @retval None ++ */ ++#define __HAL_I2C_CLEAR_ADDRFLAG(__HANDLE__) \ ++ do{ \ ++ __IO uint32_t tmpreg = 0x00U; \ ++ tmpreg = (__HANDLE__)->Instance->SR1; \ ++ tmpreg = (__HANDLE__)->Instance->SR2; \ ++ UNUSED(tmpreg); \ ++ } while(0) ++ ++/** @brief Clears the I2C STOPF pending flag. ++ * @param __HANDLE__ specifies the I2C Handle. ++ * This parameter can be I2C where x: 1, 2, or 3 to select the I2C peripheral. ++ * @retval None ++ */ ++#define __HAL_I2C_CLEAR_STOPFLAG(__HANDLE__) \ ++ do{ \ ++ __IO uint32_t tmpreg = 0x00U; \ ++ tmpreg = (__HANDLE__)->Instance->SR1; \ ++ (__HANDLE__)->Instance->CR1 |= I2C_CR1_PE; \ ++ UNUSED(tmpreg); \ ++ } while(0) ++ ++/** @brief Enable the I2C peripheral. ++ * @param __HANDLE__ specifies the I2C Handle. ++ * This parameter can be I2Cx where x: 1 or 2 to select the I2C peripheral. ++ * @retval None ++ */ ++#define __HAL_I2C_ENABLE(__HANDLE__) ((__HANDLE__)->Instance->CR1 |= I2C_CR1_PE) ++ ++/** @brief Disable the I2C peripheral. ++ * @param __HANDLE__ specifies the I2C Handle. ++ * This parameter can be I2Cx where x: 1 or 2 to select the I2C peripheral. ++ * @retval None ++ */ ++#define __HAL_I2C_DISABLE(__HANDLE__) ((__HANDLE__)->Instance->CR1 &= ~I2C_CR1_PE) ++ ++/** ++ * @} ++ */ ++ ++/* Include I2C HAL Extension module */ ++#include "stm32f4xx_hal_i2c_ex.h" ++ ++/* Exported functions --------------------------------------------------------*/ ++/** @addtogroup I2C_Exported_Functions ++ * @{ ++ */ ++ ++/** @addtogroup I2C_Exported_Functions_Group1 ++ * @{ ++ */ ++/* Initialization/de-initialization functions **********************************/ ++HAL_StatusTypeDef HAL_I2C_Init(I2C_HandleTypeDef *hi2c); ++HAL_StatusTypeDef HAL_I2C_DeInit (I2C_HandleTypeDef *hi2c); ++void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c); ++void HAL_I2C_MspDeInit(I2C_HandleTypeDef *hi2c); ++/** ++ * @} ++ */ ++ ++/** @addtogroup I2C_Exported_Functions_Group2 ++ * @{ ++ */ ++/* I/O operation functions *****************************************************/ ++/******* Blocking mode: Polling */ ++HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); ++HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout); ++HAL_StatusTypeDef HAL_I2C_Slave_Transmit(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout); ++HAL_StatusTypeDef HAL_I2C_Slave_Receive(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout); ++HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout); ++HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout); ++HAL_StatusTypeDef HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout); ++ ++/******* Non-Blocking mode: Interrupt */ ++HAL_StatusTypeDef HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); ++HAL_StatusTypeDef HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); ++HAL_StatusTypeDef HAL_I2C_Slave_Transmit_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size); ++HAL_StatusTypeDef HAL_I2C_Slave_Receive_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size); ++HAL_StatusTypeDef HAL_I2C_Mem_Write_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size); ++HAL_StatusTypeDef HAL_I2C_Mem_Read_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size); ++ ++HAL_StatusTypeDef HAL_I2C_Master_Sequential_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions); ++HAL_StatusTypeDef HAL_I2C_Master_Sequential_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions); ++HAL_StatusTypeDef HAL_I2C_Slave_Sequential_Transmit_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t XferOptions); ++HAL_StatusTypeDef HAL_I2C_Slave_Sequential_Receive_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t XferOptions); ++HAL_StatusTypeDef HAL_I2C_Master_Abort_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress); ++HAL_StatusTypeDef HAL_I2C_EnableListen_IT(I2C_HandleTypeDef *hi2c); ++HAL_StatusTypeDef HAL_I2C_DisableListen_IT(I2C_HandleTypeDef *hi2c); ++ ++/******* Non-Blocking mode: DMA */ ++HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); ++HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size); ++HAL_StatusTypeDef HAL_I2C_Slave_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size); ++HAL_StatusTypeDef HAL_I2C_Slave_Receive_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size); ++HAL_StatusTypeDef HAL_I2C_Mem_Write_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size); ++HAL_StatusTypeDef HAL_I2C_Mem_Read_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size); ++ ++/******* I2C IRQHandler and Callbacks used in non blocking modes (Interrupt and DMA) */ ++void HAL_I2C_EV_IRQHandler(I2C_HandleTypeDef *hi2c); ++void HAL_I2C_ER_IRQHandler(I2C_HandleTypeDef *hi2c); ++void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c); ++void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c); ++void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c); ++void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c); ++void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode); ++void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef *hi2c); ++void HAL_I2C_MemTxCpltCallback(I2C_HandleTypeDef *hi2c); ++void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c); ++void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c); ++void HAL_I2C_AbortCpltCallback(I2C_HandleTypeDef *hi2c); ++/** ++ * @} ++ */ ++ ++/** @addtogroup I2C_Exported_Functions_Group3 ++ * @{ ++ */ ++/* Peripheral State, Mode and Errors functions *********************************/ ++HAL_I2C_StateTypeDef HAL_I2C_GetState(I2C_HandleTypeDef *hi2c); ++HAL_I2C_ModeTypeDef HAL_I2C_GetMode(I2C_HandleTypeDef *hi2c); ++uint32_t HAL_I2C_GetError(I2C_HandleTypeDef *hi2c); ++ ++/** ++ * @} ++ */ ++ ++/** ++ * @} ++ */ ++/* Private types -------------------------------------------------------------*/ ++/* Private variables ---------------------------------------------------------*/ ++/* Private constants ---------------------------------------------------------*/ ++/** @defgroup I2C_Private_Constants I2C Private Constants ++ * @{ ++ */ ++#define I2C_FLAG_MASK 0x0000FFFFU ++/** ++ * @} ++ */ ++ ++/* Private macros ------------------------------------------------------------*/ ++/** @defgroup I2C_Private_Macros I2C Private Macros ++ * @{ ++ */ ++ ++#define I2C_FREQRANGE(__PCLK__) ((__PCLK__)/1000000U) ++#define I2C_RISE_TIME(__FREQRANGE__, __SPEED__) (((__SPEED__) <= 100000U) ? ((__FREQRANGE__) + 1U) : ((((__FREQRANGE__) * 300U) / 1000U) + 1U)) ++#define I2C_SPEED_STANDARD(__PCLK__, __SPEED__) (((((__PCLK__)/((__SPEED__) << 1U)) & I2C_CCR_CCR) < 4U)? 4U:((__PCLK__) / ((__SPEED__) << 1U))) ++#define I2C_SPEED_FAST(__PCLK__, __SPEED__, __DUTYCYCLE__) (((__DUTYCYCLE__) == I2C_DUTYCYCLE_2)? ((__PCLK__) / ((__SPEED__) * 3U)) : (((__PCLK__) / ((__SPEED__) * 25U)) | I2C_DUTYCYCLE_16_9)) ++#define I2C_SPEED(__PCLK__, __SPEED__, __DUTYCYCLE__) (((__SPEED__) <= 100000U)? (I2C_SPEED_STANDARD((__PCLK__), (__SPEED__))) : \ ++ ((I2C_SPEED_FAST((__PCLK__), (__SPEED__), (__DUTYCYCLE__)) & I2C_CCR_CCR) == 0U)? 1U : \ ++ ((I2C_SPEED_FAST((__PCLK__), (__SPEED__), (__DUTYCYCLE__))) | I2C_CCR_FS)) ++ ++#define I2C_7BIT_ADD_WRITE(__ADDRESS__) ((uint8_t)((__ADDRESS__) & (~I2C_OAR1_ADD0))) ++#define I2C_7BIT_ADD_READ(__ADDRESS__) ((uint8_t)((__ADDRESS__) | I2C_OAR1_ADD0)) ++ ++#define I2C_10BIT_ADDRESS(__ADDRESS__) ((uint8_t)((uint16_t)((__ADDRESS__) & (uint16_t)0x00FF))) ++#define I2C_10BIT_HEADER_WRITE(__ADDRESS__) ((uint8_t)((uint16_t)((uint16_t)(((uint16_t)((__ADDRESS__) & (uint16_t)0x0300)) >> 7) | (uint16_t)0x00F0))) ++#define I2C_10BIT_HEADER_READ(__ADDRESS__) ((uint8_t)((uint16_t)((uint16_t)(((uint16_t)((__ADDRESS__) & (uint16_t)0x0300)) >> 7) | (uint16_t)(0x00F1)))) ++ ++#define I2C_MEM_ADD_MSB(__ADDRESS__) ((uint8_t)((uint16_t)(((uint16_t)((__ADDRESS__) & (uint16_t)0xFF00)) >> 8))) ++#define I2C_MEM_ADD_LSB(__ADDRESS__) ((uint8_t)((uint16_t)((__ADDRESS__) & (uint16_t)0x00FF))) ++ ++/** @defgroup I2C_IS_RTC_Definitions I2C Private macros to check input parameters ++ * @{ ++ */ ++#define IS_I2C_DUTY_CYCLE(CYCLE) (((CYCLE) == I2C_DUTYCYCLE_2) || \ ++ ((CYCLE) == I2C_DUTYCYCLE_16_9)) ++#define IS_I2C_ADDRESSING_MODE(ADDRESS) (((ADDRESS) == I2C_ADDRESSINGMODE_7BIT) || \ ++ ((ADDRESS) == I2C_ADDRESSINGMODE_10BIT)) ++#define IS_I2C_DUAL_ADDRESS(ADDRESS) (((ADDRESS) == I2C_DUALADDRESS_DISABLE) || \ ++ ((ADDRESS) == I2C_DUALADDRESS_ENABLE)) ++#define IS_I2C_GENERAL_CALL(CALL) (((CALL) == I2C_GENERALCALL_DISABLE) || \ ++ ((CALL) == I2C_GENERALCALL_ENABLE)) ++#define IS_I2C_NO_STRETCH(STRETCH) (((STRETCH) == I2C_NOSTRETCH_DISABLE) || \ ++ ((STRETCH) == I2C_NOSTRETCH_ENABLE)) ++#define IS_I2C_MEMADD_SIZE(SIZE) (((SIZE) == I2C_MEMADD_SIZE_8BIT) || \ ++ ((SIZE) == I2C_MEMADD_SIZE_16BIT)) ++#define IS_I2C_CLOCK_SPEED(SPEED) (((SPEED) > 0U) && ((SPEED) <= 400000U)) ++#define IS_I2C_OWN_ADDRESS1(ADDRESS1) (((ADDRESS1) & 0xFFFFFC00U) == 0U) ++#define IS_I2C_OWN_ADDRESS2(ADDRESS2) (((ADDRESS2) & 0xFFFFFF01U) == 0U) ++#define IS_I2C_TRANSFER_OPTIONS_REQUEST(REQUEST) (((REQUEST) == I2C_FIRST_FRAME) || \ ++ ((REQUEST) == I2C_NEXT_FRAME) || \ ++ ((REQUEST) == I2C_FIRST_AND_LAST_FRAME) || \ ++ ((REQUEST) == I2C_LAST_FRAME)) ++/** ++ * @} ++ */ ++ ++/** ++ * @} ++ */ ++ ++/* Private functions ---------------------------------------------------------*/ ++/** @defgroup I2C_Private_Functions I2C Private Functions ++ * @{ ++ */ ++ ++/** ++ * @} ++ */ ++ ++/** ++ * @} ++ */ ++ ++/** ++ * @} ++ */ ++ ++#ifdef __cplusplus ++} ++#endif ++ ++ ++#endif /* __STM32F4xx_HAL_I2C_H */ ++ ++/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ +diff --git a/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c_ex.h b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c_ex.h +new file mode 100644 +index 0000000..ff47d5c +--- /dev/null ++++ b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c_ex.h +@@ -0,0 +1,137 @@ ++/** ++ ****************************************************************************** ++ * @file stm32f4xx_hal_i2c_ex.h ++ * @author MCD Application Team ++ * @brief Header file of I2C HAL Extension module. ++ ****************************************************************************** ++ * @attention ++ * ++ *

© COPYRIGHT(c) 2017 STMicroelectronics

++ * ++ * Redistribution and use in source and binary forms, with or without modification, ++ * are permitted provided that the following conditions are met: ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright notice, ++ * this list of conditions and the following disclaimer in the documentation ++ * and/or other materials provided with the distribution. ++ * 3. Neither the name of STMicroelectronics nor the names of its contributors ++ * may be used to endorse or promote products derived from this software ++ * without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE ++ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ++ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR ++ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER ++ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ++ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ * ++ ****************************************************************************** ++ */ ++ ++/* Define to prevent recursive inclusion -------------------------------------*/ ++#ifndef __STM32F4xx_HAL_I2C_EX_H ++#define __STM32F4xx_HAL_I2C_EX_H ++ ++#ifdef __cplusplus ++ extern "C" { ++#endif ++ ++#if defined(STM32F427xx) || defined(STM32F437xx) || defined(STM32F429xx) || defined(STM32F439xx) ||\ ++ defined(STM32F401xC) || defined(STM32F401xE) || defined(STM32F411xE) || defined(STM32F446xx) ||\ ++ defined(STM32F469xx) || defined(STM32F479xx) || defined(STM32F413xx) || defined(STM32F423xx) ++/* Includes ------------------------------------------------------------------*/ ++#include "stm32f4xx_hal_def.h" ++ ++/** @addtogroup STM32F4xx_HAL_Driver ++ * @{ ++ */ ++ ++/** @addtogroup I2CEx ++ * @{ ++ */ ++ ++/* Exported types ------------------------------------------------------------*/ ++/* Exported constants --------------------------------------------------------*/ ++/** @defgroup I2CEx_Exported_Constants I2C Exported Constants ++ * @{ ++ */ ++ ++/** @defgroup I2CEx_Analog_Filter I2C Analog Filter ++ * @{ ++ */ ++#define I2C_ANALOGFILTER_ENABLE 0x00000000U ++#define I2C_ANALOGFILTER_DISABLE I2C_FLTR_ANOFF ++/** ++ * @} ++ */ ++ ++/** ++ * @} ++ */ ++ ++/* Exported macro ------------------------------------------------------------*/ ++/* Exported functions --------------------------------------------------------*/ ++/** @addtogroup I2CEx_Exported_Functions ++ * @{ ++ */ ++ ++/** @addtogroup I2CEx_Exported_Functions_Group1 ++ * @{ ++ */ ++/* Peripheral Control functions ************************************************/ ++HAL_StatusTypeDef HAL_I2CEx_ConfigAnalogFilter(I2C_HandleTypeDef *hi2c, uint32_t AnalogFilter); ++HAL_StatusTypeDef HAL_I2CEx_ConfigDigitalFilter(I2C_HandleTypeDef *hi2c, uint32_t DigitalFilter); ++/** ++ * @} ++ */ ++ ++/** ++ * @} ++ */ ++/* Private types -------------------------------------------------------------*/ ++/* Private variables ---------------------------------------------------------*/ ++/* Private constants ---------------------------------------------------------*/ ++/** @defgroup I2CEx_Private_Constants I2C Private Constants ++ * @{ ++ */ ++ ++/** ++ * @} ++ */ ++ ++/* Private macros ------------------------------------------------------------*/ ++/** @defgroup I2CEx_Private_Macros I2C Private Macros ++ * @{ ++ */ ++#define IS_I2C_ANALOG_FILTER(FILTER) (((FILTER) == I2C_ANALOGFILTER_ENABLE) || \ ++ ((FILTER) == I2C_ANALOGFILTER_DISABLE)) ++#define IS_I2C_DIGITAL_FILTER(FILTER) ((FILTER) <= 0x0000000FU) ++/** ++ * @} ++ */ ++ ++/** ++ * @} ++ */ ++ ++/** ++ * @} ++ */ ++ ++#endif /* STM32F427xx || STM32F429xx || STM32F437xx || STM32F439xx || STM32F401xC ||\ ++ STM32F401xE || STM32F411xE || STM32F446xx || STM32F469xx || STM32F479xx ||\ ++ STM32F413xx || STM32F423xx */ ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif /* __STM32F4xx_HAL_I2C_EX_H */ ++ ++ ++/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ +diff --git a/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c.c b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c.c +new file mode 100644 +index 0000000..da18520 +--- /dev/null ++++ b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c.c +@@ -0,0 +1,5494 @@ ++/** ++ ****************************************************************************** ++ * @file stm32f4xx_hal_i2c.c ++ * @author MCD Application Team ++ * @brief I2C HAL module driver. ++ * This file provides firmware functions to manage the following ++ * functionalities of the Inter Integrated Circuit (I2C) peripheral: ++ * + Initialization and de-initialization functions ++ * + IO operation functions ++ * + Peripheral State, Mode and Error functions ++ * ++ @verbatim ++ ============================================================================== ++ ##### How to use this driver ##### ++ ============================================================================== ++ [..] ++ The I2C HAL driver can be used as follows: ++ ++ (#) Declare a I2C_HandleTypeDef handle structure, for example: ++ I2C_HandleTypeDef hi2c; ++ ++ (#)Initialize the I2C low level resources by implementing the HAL_I2C_MspInit() API: ++ (##) Enable the I2Cx interface clock ++ (##) I2C pins configuration ++ (+++) Enable the clock for the I2C GPIOs ++ (+++) Configure I2C pins as alternate function open-drain ++ (##) NVIC configuration if you need to use interrupt process ++ (+++) Configure the I2Cx interrupt priority ++ (+++) Enable the NVIC I2C IRQ Channel ++ (##) DMA Configuration if you need to use DMA process ++ (+++) Declare a DMA_HandleTypeDef handle structure for the transmit or receive stream ++ (+++) Enable the DMAx interface clock using ++ (+++) Configure the DMA handle parameters ++ (+++) Configure the DMA Tx or Rx Stream ++ (+++) Associate the initialized DMA handle to the hi2c DMA Tx or Rx handle ++ (+++) Configure the priority and enable the NVIC for the transfer complete interrupt on ++ the DMA Tx or Rx Stream ++ ++ (#) Configure the Communication Speed, Duty cycle, Addressing mode, Own Address1, ++ Dual Addressing mode, Own Address2, General call and Nostretch mode in the hi2c Init structure. ++ ++ (#) Initialize the I2C registers by calling the HAL_I2C_Init(), configures also the low level Hardware ++ (GPIO, CLOCK, NVIC...etc) by calling the customized HAL_I2C_MspInit(&hi2c) API. ++ ++ (#) To check if target device is ready for communication, use the function HAL_I2C_IsDeviceReady() ++ ++ (#) For I2C IO and IO MEM operations, three operation modes are available within this driver : ++ ++ *** Polling mode IO operation *** ++ ================================= ++ [..] ++ (+) Transmit in master mode an amount of data in blocking mode using HAL_I2C_Master_Transmit() ++ (+) Receive in master mode an amount of data in blocking mode using HAL_I2C_Master_Receive() ++ (+) Transmit in slave mode an amount of data in blocking mode using HAL_I2C_Slave_Transmit() ++ (+) Receive in slave mode an amount of data in blocking mode using HAL_I2C_Slave_Receive() ++ ++ *** Polling mode IO MEM operation *** ++ ===================================== ++ [..] ++ (+) Write an amount of data in blocking mode to a specific memory address using HAL_I2C_Mem_Write() ++ (+) Read an amount of data in blocking mode from a specific memory address using HAL_I2C_Mem_Read() ++ ++ ++ *** Interrupt mode IO operation *** ++ =================================== ++ [..] ++ (+) Transmit in master mode an amount of data in non blocking mode using HAL_I2C_Master_Transmit_IT() ++ (+) At transmission end of transfer HAL_I2C_MasterTxCpltCallback is executed and user can ++ add his own code by customization of function pointer HAL_I2C_MasterTxCpltCallback ++ (+) Receive in master mode an amount of data in non blocking mode using HAL_I2C_Master_Receive_IT() ++ (+) At reception end of transfer HAL_I2C_MasterRxCpltCallback is executed and user can ++ add his own code by customization of function pointer HAL_I2C_MasterRxCpltCallback ++ (+) Transmit in slave mode an amount of data in non blocking mode using HAL_I2C_Slave_Transmit_IT() ++ (+) At transmission end of transfer HAL_I2C_SlaveTxCpltCallback is executed and user can ++ add his own code by customization of function pointer HAL_I2C_SlaveTxCpltCallback ++ (+) Receive in slave mode an amount of data in non blocking mode using HAL_I2C_Slave_Receive_IT() ++ (+) At reception end of transfer HAL_I2C_SlaveRxCpltCallback is executed and user can ++ add his own code by customization of function pointer HAL_I2C_SlaveRxCpltCallback ++ (+) In case of transfer Error, HAL_I2C_ErrorCallback() function is executed and user can ++ add his own code by customization of function pointer HAL_I2C_ErrorCallback ++ (+) Abort a master I2C process communication with Interrupt using HAL_I2C_Master_Abort_IT() ++ (+) End of abort process, HAL_I2C_AbortCpltCallback() is executed and user can ++ add his own code by customization of function pointer HAL_I2C_AbortCpltCallback() ++ ++ *** Interrupt mode IO sequential operation *** ++ ============================================== ++ [..] ++ (@) These interfaces allow to manage a sequential transfer with a repeated start condition ++ when a direction change during transfer ++ [..] ++ (+) A specific option field manage the different steps of a sequential transfer ++ (+) Option field values are defined through @ref I2C_XFEROPTIONS and are listed below: ++ (++) I2C_FIRST_AND_LAST_FRAME: No sequential usage, functionnal is same as associated interfaces in no sequential mode ++ (++) I2C_FIRST_FRAME: Sequential usage, this option allow to manage a sequence with start condition, address ++ and data to transfer without a final stop condition ++ (++) I2C_NEXT_FRAME: Sequential usage, this option allow to manage a sequence with a restart condition, address ++ and with new data to transfer if the direction change or manage only the new data to transfer ++ if no direction change and without a final stop condition in both cases ++ (++) I2C_LAST_FRAME: Sequential usage, this option allow to manage a sequance with a restart condition, address ++ and with new data to transfer if the direction change or manage only the new data to transfer ++ if no direction change and with a final stop condition in both cases ++ ++ (+) Differents sequential I2C interfaces are listed below: ++ (++) Sequential transmit in master I2C mode an amount of data in non-blocking mode using HAL_I2C_Master_Sequential_Transmit_IT() ++ (+++) At transmission end of current frame transfer, HAL_I2C_MasterTxCpltCallback() is executed and user can ++ add his own code by customization of function pointer HAL_I2C_MasterTxCpltCallback() ++ (++) Sequential receive in master I2C mode an amount of data in non-blocking mode using HAL_I2C_Master_Sequential_Receive_IT() ++ (+++) At reception end of current frame transfer, HAL_I2C_MasterRxCpltCallback() is executed and user can ++ add his own code by customization of function pointer HAL_I2C_MasterRxCpltCallback() ++ (++) Abort a master I2C process communication with Interrupt using HAL_I2C_Master_Abort_IT() ++ (+++) End of abort process, HAL_I2C_AbortCpltCallback() is executed and user can ++ add his own code by customization of function pointer HAL_I2C_AbortCpltCallback() ++ (++) Enable/disable the Address listen mode in slave I2C mode using HAL_I2C_EnableListen_IT() HAL_I2C_DisableListen_IT() ++ (+++) When address slave I2C match, HAL_I2C_AddrCallback() is executed and user can ++ add his own code to check the Address Match Code and the transmission direction request by master (Write/Read). ++ (+++) At Listen mode end HAL_I2C_ListenCpltCallback() is executed and user can ++ add his own code by customization of function pointer HAL_I2C_ListenCpltCallback() ++ (++) Sequential transmit in slave I2C mode an amount of data in non-blocking mode using HAL_I2C_Slave_Sequential_Transmit_IT() ++ (+++) At transmission end of current frame transfer, HAL_I2C_SlaveTxCpltCallback() is executed and user can ++ add his own code by customization of function pointer HAL_I2C_SlaveTxCpltCallback() ++ (++) Sequential receive in slave I2C mode an amount of data in non-blocking mode using HAL_I2C_Slave_Sequential_Receive_IT() ++ (+++) At reception end of current frame transfer, HAL_I2C_SlaveRxCpltCallback() is executed and user can ++ add his own code by customization of function pointer HAL_I2C_SlaveRxCpltCallback() ++ (++) In case of transfer Error, HAL_I2C_ErrorCallback() function is executed and user can ++ add his own code by customization of function pointer HAL_I2C_ErrorCallback() ++ (++) Abort a master I2C process communication with Interrupt using HAL_I2C_Master_Abort_IT() ++ (++) End of abort process, HAL_I2C_AbortCpltCallback() is executed and user can ++ add his own code by customization of function pointer HAL_I2C_AbortCpltCallback() ++ ++ *** Interrupt mode IO MEM operation *** ++ ======================================= ++ [..] ++ (+) Write an amount of data in no-blocking mode with Interrupt to a specific memory address using ++ HAL_I2C_Mem_Write_IT() ++ (+) At MEM end of write transfer HAL_I2C_MemTxCpltCallback is executed and user can ++ add his own code by customization of function pointer HAL_I2C_MemTxCpltCallback ++ (+) Read an amount of data in no-blocking mode with Interrupt from a specific memory address using ++ HAL_I2C_Mem_Read_IT() ++ (+) At MEM end of read transfer HAL_I2C_MemRxCpltCallback is executed and user can ++ add his own code by customization of function pointer HAL_I2C_MemRxCpltCallback ++ (+) In case of transfer Error, HAL_I2C_ErrorCallback() function is executed and user can ++ add his own code by customization of function pointer HAL_I2C_ErrorCallback ++ ++ *** DMA mode IO operation *** ++ ============================== ++ [..] ++ (+) Transmit in master mode an amount of data in non blocking mode (DMA) using ++ HAL_I2C_Master_Transmit_DMA() ++ (+) At transmission end of transfer HAL_I2C_MasterTxCpltCallback is executed and user can ++ add his own code by customization of function pointer HAL_I2C_MasterTxCpltCallback ++ (+) Receive in master mode an amount of data in non blocking mode (DMA) using ++ HAL_I2C_Master_Receive_DMA() ++ (+) At reception end of transfer HAL_I2C_MasterRxCpltCallback is executed and user can ++ add his own code by customization of function pointer HAL_I2C_MasterRxCpltCallback ++ (+) Transmit in slave mode an amount of data in non blocking mode (DMA) using ++ HAL_I2C_Slave_Transmit_DMA() ++ (+) At transmission end of transfer HAL_I2C_SlaveTxCpltCallback is executed and user can ++ add his own code by customization of function pointer HAL_I2C_SlaveTxCpltCallback ++ (+) Receive in slave mode an amount of data in non blocking mode (DMA) using ++ HAL_I2C_Slave_Receive_DMA() ++ (+) At reception end of transfer HAL_I2C_SlaveRxCpltCallback is executed and user can ++ add his own code by customization of function pointer HAL_I2C_SlaveRxCpltCallback ++ (+) In case of transfer Error, HAL_I2C_ErrorCallback() function is executed and user can ++ add his own code by customization of function pointer HAL_I2C_ErrorCallback ++ (+) Abort a master I2C process communication with Interrupt using HAL_I2C_Master_Abort_IT() ++ (+) End of abort process, HAL_I2C_AbortCpltCallback() is executed and user can ++ add his own code by customization of function pointer HAL_I2C_AbortCpltCallback() ++ ++ *** DMA mode IO MEM operation *** ++ ================================= ++ [..] ++ (+) Write an amount of data in no-blocking mode with DMA to a specific memory address using ++ HAL_I2C_Mem_Write_DMA() ++ (+) At MEM end of write transfer HAL_I2C_MemTxCpltCallback is executed and user can ++ add his own code by customization of function pointer HAL_I2C_MemTxCpltCallback ++ (+) Read an amount of data in no-blocking mode with DMA from a specific memory address using ++ HAL_I2C_Mem_Read_DMA() ++ (+) At MEM end of read transfer HAL_I2C_MemRxCpltCallback is executed and user can ++ add his own code by customization of function pointer HAL_I2C_MemRxCpltCallback ++ (+) In case of transfer Error, HAL_I2C_ErrorCallback() function is executed and user can ++ add his own code by customization of function pointer HAL_I2C_ErrorCallback ++ ++ ++ *** I2C HAL driver macros list *** ++ ================================== ++ [..] ++ Below the list of most used macros in I2C HAL driver. ++ ++ (+) __HAL_I2C_ENABLE: Enable the I2C peripheral ++ (+) __HAL_I2C_DISABLE: Disable the I2C peripheral ++ (+) __HAL_I2C_GET_FLAG : Checks whether the specified I2C flag is set or not ++ (+) __HAL_I2C_CLEAR_FLAG : Clear the specified I2C pending flag ++ (+) __HAL_I2C_ENABLE_IT: Enable the specified I2C interrupt ++ (+) __HAL_I2C_DISABLE_IT: Disable the specified I2C interrupt ++ ++ [..] ++ (@) You can refer to the I2C HAL driver header file for more useful macros ++ ++ ++ @endverbatim ++ ****************************************************************************** ++ * @attention ++ * ++ *

© COPYRIGHT(c) 2017 STMicroelectronics

++ * ++ * Redistribution and use in source and binary forms, with or without modification, ++ * are permitted provided that the following conditions are met: ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright notice, ++ * this list of conditions and the following disclaimer in the documentation ++ * and/or other materials provided with the distribution. ++ * 3. Neither the name of STMicroelectronics nor the names of its contributors ++ * may be used to endorse or promote products derived from this software ++ * without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE ++ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ++ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR ++ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER ++ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ++ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ * ++ ****************************************************************************** ++ */ ++ ++/* Includes ------------------------------------------------------------------*/ ++#include "stm32f4xx_hal.h" ++ ++/** @addtogroup STM32F4xx_HAL_Driver ++ * @{ ++ */ ++ ++/** @defgroup I2C I2C ++ * @brief I2C HAL module driver ++ * @{ ++ */ ++ ++#ifdef HAL_I2C_MODULE_ENABLED ++ ++/* Private typedef -----------------------------------------------------------*/ ++/* Private define ------------------------------------------------------------*/ ++/** @addtogroup I2C_Private_Define ++ * @{ ++ */ ++#define I2C_TIMEOUT_FLAG 35U /*!< Timeout 35 ms */ ++#define I2C_TIMEOUT_BUSY_FLAG 25U /*!< Timeout 25 ms */ ++#define I2C_NO_OPTION_FRAME 0xFFFF0000U /*!< XferOptions default value */ ++ ++/* Private define for @ref PreviousState usage */ ++#define I2C_STATE_MSK ((uint32_t)((HAL_I2C_STATE_BUSY_TX | HAL_I2C_STATE_BUSY_RX) & (~(uint32_t)HAL_I2C_STATE_READY))) /*!< Mask State define, keep only RX and TX bits */ ++#define I2C_STATE_NONE ((uint32_t)(HAL_I2C_MODE_NONE)) /*!< Default Value */ ++#define I2C_STATE_MASTER_BUSY_TX ((uint32_t)((HAL_I2C_STATE_BUSY_TX & I2C_STATE_MSK) | HAL_I2C_MODE_MASTER)) /*!< Master Busy TX, combinaison of State LSB and Mode enum */ ++#define I2C_STATE_MASTER_BUSY_RX ((uint32_t)((HAL_I2C_STATE_BUSY_RX & I2C_STATE_MSK) | HAL_I2C_MODE_MASTER)) /*!< Master Busy RX, combinaison of State LSB and Mode enum */ ++#define I2C_STATE_SLAVE_BUSY_TX ((uint32_t)((HAL_I2C_STATE_BUSY_TX & I2C_STATE_MSK) | HAL_I2C_MODE_SLAVE)) /*!< Slave Busy TX, combinaison of State LSB and Mode enum */ ++#define I2C_STATE_SLAVE_BUSY_RX ((uint32_t)((HAL_I2C_STATE_BUSY_RX & I2C_STATE_MSK) | HAL_I2C_MODE_SLAVE)) /*!< Slave Busy RX, combinaison of State LSB and Mode enum */ ++ ++/** ++ * @} ++ */ ++ ++/* Private macro -------------------------------------------------------------*/ ++/* Private variables ---------------------------------------------------------*/ ++/* Private function prototypes -----------------------------------------------*/ ++/** @addtogroup I2C_Private_Functions ++ * @{ ++ */ ++/* Private functions to handle DMA transfer */ ++static void I2C_DMAXferCplt(DMA_HandleTypeDef *hdma); ++static void I2C_DMAError(DMA_HandleTypeDef *hdma); ++static void I2C_DMAAbort(DMA_HandleTypeDef *hdma); ++ ++static void I2C_ITError(I2C_HandleTypeDef *hi2c); ++ ++static HAL_StatusTypeDef I2C_MasterRequestWrite(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Timeout, uint32_t Tickstart); ++static HAL_StatusTypeDef I2C_MasterRequestRead(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Timeout, uint32_t Tickstart); ++static HAL_StatusTypeDef I2C_RequestMemoryWrite(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint32_t Timeout, uint32_t Tickstart); ++static HAL_StatusTypeDef I2C_RequestMemoryRead(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint32_t Timeout, uint32_t Tickstart); ++static HAL_StatusTypeDef I2C_WaitOnFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Flag, FlagStatus Status, uint32_t Timeout, uint32_t Tickstart); ++static HAL_StatusTypeDef I2C_WaitOnMasterAddressFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Flag, uint32_t Timeout, uint32_t Tickstart); ++static HAL_StatusTypeDef I2C_WaitOnTXEFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout, uint32_t Tickstart); ++static HAL_StatusTypeDef I2C_WaitOnBTFFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout, uint32_t Tickstart); ++static HAL_StatusTypeDef I2C_WaitOnRXNEFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout, uint32_t Tickstart); ++static HAL_StatusTypeDef I2C_WaitOnSTOPFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout, uint32_t Tickstart); ++static HAL_StatusTypeDef I2C_IsAcknowledgeFailed(I2C_HandleTypeDef *hi2c); ++ ++/* Private functions for I2C transfer IRQ handler */ ++static HAL_StatusTypeDef I2C_MasterTransmit_TXE(I2C_HandleTypeDef *hi2c); ++static HAL_StatusTypeDef I2C_MasterTransmit_BTF(I2C_HandleTypeDef *hi2c); ++static HAL_StatusTypeDef I2C_MasterReceive_RXNE(I2C_HandleTypeDef *hi2c); ++static HAL_StatusTypeDef I2C_MasterReceive_BTF(I2C_HandleTypeDef *hi2c); ++static HAL_StatusTypeDef I2C_Master_SB(I2C_HandleTypeDef *hi2c); ++static HAL_StatusTypeDef I2C_Master_ADD10(I2C_HandleTypeDef *hi2c); ++static HAL_StatusTypeDef I2C_Master_ADDR(I2C_HandleTypeDef *hi2c); ++ ++static HAL_StatusTypeDef I2C_SlaveTransmit_TXE(I2C_HandleTypeDef *hi2c); ++static HAL_StatusTypeDef I2C_SlaveTransmit_BTF(I2C_HandleTypeDef *hi2c); ++static HAL_StatusTypeDef I2C_SlaveReceive_RXNE(I2C_HandleTypeDef *hi2c); ++static HAL_StatusTypeDef I2C_SlaveReceive_BTF(I2C_HandleTypeDef *hi2c); ++static HAL_StatusTypeDef I2C_Slave_ADDR(I2C_HandleTypeDef *hi2c); ++static HAL_StatusTypeDef I2C_Slave_STOPF(I2C_HandleTypeDef *hi2c); ++static HAL_StatusTypeDef I2C_Slave_AF(I2C_HandleTypeDef *hi2c); ++/** ++ * @} ++ */ ++ ++/* Exported functions --------------------------------------------------------*/ ++/** @defgroup I2C_Exported_Functions I2C Exported Functions ++ * @{ ++ */ ++ ++/** @defgroup I2C_Exported_Functions_Group1 Initialization and de-initialization functions ++ * @brief Initialization and Configuration functions ++ * ++@verbatim ++ =============================================================================== ++ ##### Initialization and de-initialization functions ##### ++ =============================================================================== ++ [..] This subsection provides a set of functions allowing to initialize and ++ de-initialize the I2Cx peripheral: ++ ++ (+) User must Implement HAL_I2C_MspInit() function in which he configures ++ all related peripherals resources (CLOCK, GPIO, DMA, IT and NVIC). ++ ++ (+) Call the function HAL_I2C_Init() to configure the selected device with ++ the selected configuration: ++ (++) Communication Speed ++ (++) Duty cycle ++ (++) Addressing mode ++ (++) Own Address 1 ++ (++) Dual Addressing mode ++ (++) Own Address 2 ++ (++) General call mode ++ (++) Nostretch mode ++ ++ (+) Call the function HAL_I2C_DeInit() to restore the default configuration ++ of the selected I2Cx peripheral. ++ ++@endverbatim ++ * @{ ++ */ ++ ++/** ++ * @brief Initializes the I2C according to the specified parameters ++ * in the I2C_InitTypeDef and create the associated handle. ++ * @param hi2c pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Init(I2C_HandleTypeDef *hi2c) ++{ ++ uint32_t freqrange = 0U; ++ uint32_t pclk1 = 0U; ++ ++ /* Check the I2C handle allocation */ ++ if(hi2c == NULL) ++ { ++ return HAL_ERROR; ++ } ++ ++ /* Check the parameters */ ++ assert_param(IS_I2C_ALL_INSTANCE(hi2c->Instance)); ++ assert_param(IS_I2C_CLOCK_SPEED(hi2c->Init.ClockSpeed)); ++ assert_param(IS_I2C_DUTY_CYCLE(hi2c->Init.DutyCycle)); ++ assert_param(IS_I2C_OWN_ADDRESS1(hi2c->Init.OwnAddress1)); ++ assert_param(IS_I2C_ADDRESSING_MODE(hi2c->Init.AddressingMode)); ++ assert_param(IS_I2C_DUAL_ADDRESS(hi2c->Init.DualAddressMode)); ++ assert_param(IS_I2C_OWN_ADDRESS2(hi2c->Init.OwnAddress2)); ++ assert_param(IS_I2C_GENERAL_CALL(hi2c->Init.GeneralCallMode)); ++ assert_param(IS_I2C_NO_STRETCH(hi2c->Init.NoStretchMode)); ++ ++ if(hi2c->State == HAL_I2C_STATE_RESET) ++ { ++ /* Allocate lock resource and initialize it */ ++ hi2c->Lock = HAL_UNLOCKED; ++ /* Init the low level hardware : GPIO, CLOCK, NVIC */ ++ HAL_I2C_MspInit(hi2c); ++ } ++ ++ hi2c->State = HAL_I2C_STATE_BUSY; ++ ++ /* Disable the selected I2C peripheral */ ++ __HAL_I2C_DISABLE(hi2c); ++ ++ /* Get PCLK1 frequency */ ++ pclk1 = HAL_RCC_GetPCLK1Freq(); ++ ++ /* Calculate frequency range */ ++ freqrange = I2C_FREQRANGE(pclk1); ++ ++ /*---------------------------- I2Cx CR2 Configuration ----------------------*/ ++ /* Configure I2Cx: Frequency range */ ++ hi2c->Instance->CR2 = freqrange; ++ ++ /*---------------------------- I2Cx TRISE Configuration --------------------*/ ++ /* Configure I2Cx: Rise Time */ ++ hi2c->Instance->TRISE = I2C_RISE_TIME(freqrange, hi2c->Init.ClockSpeed); ++ ++ /*---------------------------- I2Cx CCR Configuration ----------------------*/ ++ /* Configure I2Cx: Speed */ ++ hi2c->Instance->CCR = I2C_SPEED(pclk1, hi2c->Init.ClockSpeed, hi2c->Init.DutyCycle); ++ ++ /*---------------------------- I2Cx CR1 Configuration ----------------------*/ ++ /* Configure I2Cx: Generalcall and NoStretch mode */ ++ hi2c->Instance->CR1 = (hi2c->Init.GeneralCallMode | hi2c->Init.NoStretchMode); ++ ++ /*---------------------------- I2Cx OAR1 Configuration ---------------------*/ ++ /* Configure I2Cx: Own Address1 and addressing mode */ ++ hi2c->Instance->OAR1 = (hi2c->Init.AddressingMode | hi2c->Init.OwnAddress1); ++ ++ /*---------------------------- I2Cx OAR2 Configuration ---------------------*/ ++ /* Configure I2Cx: Dual mode and Own Address2 */ ++ hi2c->Instance->OAR2 = (hi2c->Init.DualAddressMode | hi2c->Init.OwnAddress2); ++ ++ /* Enable the selected I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ return HAL_OK; ++} ++ ++/** ++ * @brief DeInitializes the I2C peripheral. ++ * @param hi2c pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_DeInit(I2C_HandleTypeDef *hi2c) ++{ ++ /* Check the I2C handle allocation */ ++ if(hi2c == NULL) ++ { ++ return HAL_ERROR; ++ } ++ ++ /* Check the parameters */ ++ assert_param(IS_I2C_ALL_INSTANCE(hi2c->Instance)); ++ ++ hi2c->State = HAL_I2C_STATE_BUSY; ++ ++ /* Disable the I2C Peripheral Clock */ ++ __HAL_I2C_DISABLE(hi2c); ++ ++ /* DeInit the low level hardware: GPIO, CLOCK, NVIC */ ++ HAL_I2C_MspDeInit(hi2c); ++ ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ hi2c->State = HAL_I2C_STATE_RESET; ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ /* Release Lock */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_OK; ++} ++ ++/** ++ * @brief I2C MSP Init. ++ * @param hi2c pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval None ++ */ ++ __weak void HAL_I2C_MspInit(I2C_HandleTypeDef *hi2c) ++{ ++ /* Prevent unused argument(s) compilation warning */ ++ UNUSED(hi2c); ++ /* NOTE : This function Should not be modified, when the callback is needed, ++ the HAL_I2C_MspInit could be implemented in the user file ++ */ ++} ++ ++/** ++ * @brief I2C MSP DeInit ++ * @param hi2c pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval None ++ */ ++ __weak void HAL_I2C_MspDeInit(I2C_HandleTypeDef *hi2c) ++{ ++ /* Prevent unused argument(s) compilation warning */ ++ UNUSED(hi2c); ++ /* NOTE : This function Should not be modified, when the callback is needed, ++ the HAL_I2C_MspDeInit could be implemented in the user file ++ */ ++} ++ ++/** ++ * @} ++ */ ++ ++/** @defgroup I2C_Exported_Functions_Group2 IO operation functions ++ * @brief Data transfers functions ++ * ++@verbatim ++ =============================================================================== ++ ##### IO operation functions ##### ++ =============================================================================== ++ [..] ++ This subsection provides a set of functions allowing to manage the I2C data ++ transfers. ++ ++ (#) There are two modes of transfer: ++ (++) Blocking mode : The communication is performed in the polling mode. ++ The status of all data processing is returned by the same function ++ after finishing transfer. ++ (++) No-Blocking mode : The communication is performed using Interrupts ++ or DMA. These functions return the status of the transfer startup. ++ The end of the data processing will be indicated through the ++ dedicated I2C IRQ when using Interrupt mode or the DMA IRQ when ++ using DMA mode. ++ ++ (#) Blocking mode functions are : ++ (++) HAL_I2C_Master_Transmit() ++ (++) HAL_I2C_Master_Receive() ++ (++) HAL_I2C_Slave_Transmit() ++ (++) HAL_I2C_Slave_Receive() ++ (++) HAL_I2C_Mem_Write() ++ (++) HAL_I2C_Mem_Read() ++ (++) HAL_I2C_IsDeviceReady() ++ ++ (#) No-Blocking mode functions with Interrupt are : ++ (++) HAL_I2C_Master_Transmit_IT() ++ (++) HAL_I2C_Master_Receive_IT() ++ (++) HAL_I2C_Slave_Transmit_IT() ++ (++) HAL_I2C_Slave_Receive_IT() ++ (++) HAL_I2C_Master_Sequential_Transmit_IT() ++ (++) HAL_I2C_Master_Sequential_Receive_IT() ++ (++) HAL_I2C_Slave_Sequential_Transmit_IT() ++ (++) HAL_I2C_Slave_Sequential_Receive_IT() ++ (++) HAL_I2C_Mem_Write_IT() ++ (++) HAL_I2C_Mem_Read_IT() ++ ++ (#) No-Blocking mode functions with DMA are : ++ (++) HAL_I2C_Master_Transmit_DMA() ++ (++) HAL_I2C_Master_Receive_DMA() ++ (++) HAL_I2C_Slave_Transmit_DMA() ++ (++) HAL_I2C_Slave_Receive_DMA() ++ (++) HAL_I2C_Mem_Write_DMA() ++ (++) HAL_I2C_Mem_Read_DMA() ++ ++ (#) A set of Transfer Complete Callbacks are provided in non Blocking mode: ++ (++) HAL_I2C_MemTxCpltCallback() ++ (++) HAL_I2C_MemRxCpltCallback() ++ (++) HAL_I2C_MasterTxCpltCallback() ++ (++) HAL_I2C_MasterRxCpltCallback() ++ (++) HAL_I2C_SlaveTxCpltCallback() ++ (++) HAL_I2C_SlaveRxCpltCallback() ++ (++) HAL_I2C_ErrorCallback() ++ (++) HAL_I2C_AbortCpltCallback() ++ ++@endverbatim ++ * @{ ++ */ ++ ++/** ++ * @brief Transmits in master mode an amount of data in blocking mode. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param DevAddress Target device address The device 7 bits address value ++ * in datasheet must be shifted to the left before calling the interface ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @param Timeout Timeout duration ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout) ++{ ++ uint32_t tickstart = 0x00U; ++ ++ /* Init tickstart for timeout management*/ ++ tickstart = HAL_GetTick(); ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ /* Wait until BUSY flag is reset */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK) ++ { ++ return HAL_BUSY; ++ } ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_TX; ++ hi2c->Mode = HAL_I2C_MODE_MASTER; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->XferSize = hi2c->XferCount; ++ ++ /* Send Slave Address */ ++ if(I2C_MasterRequestWrite(hi2c, DevAddress, Timeout, tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ return HAL_ERROR; ++ } ++ else ++ { ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ while(hi2c->XferSize > 0U) ++ { ++ /* Wait until TXE flag is set */ ++ if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ /* Write data to DR */ ++ hi2c->Instance->DR = (*hi2c->pBuffPtr++); ++ hi2c->XferCount--; ++ hi2c->XferSize--; ++ ++ if((__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) && (hi2c->XferSize != 0U)) ++ { ++ /* Write data to DR */ ++ hi2c->Instance->DR = (*hi2c->pBuffPtr++); ++ hi2c->XferCount--; ++ hi2c->XferSize--; ++ } ++ ++ /* Wait until BTF flag is set */ ++ if(I2C_WaitOnBTFFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ } ++ ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Receives in master mode an amount of data in blocking mode. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param DevAddress Target device address The device 7 bits address value ++ * in datasheet must be shifted to the left before calling the interface ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @param Timeout Timeout duration ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout) ++{ ++ uint32_t tickstart = 0x00U; ++ ++ /* Init tickstart for timeout management*/ ++ tickstart = HAL_GetTick(); ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ /* Wait until BUSY flag is reset */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK) ++ { ++ return HAL_BUSY; ++ } ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_RX; ++ hi2c->Mode = HAL_I2C_MODE_MASTER; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->XferSize = hi2c->XferCount; ++ ++ /* Send Slave Address */ ++ if(I2C_MasterRequestRead(hi2c, DevAddress, Timeout, tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ return HAL_ERROR; ++ } ++ else ++ { ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ if(hi2c->XferSize == 0U) ++ { ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ } ++ else if(hi2c->XferSize == 1U) ++ { ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ } ++ else if(hi2c->XferSize == 2U) ++ { ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ /* Enable Pos */ ++ hi2c->Instance->CR1 |= I2C_CR1_POS; ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ } ++ else ++ { ++ /* Enable Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ } ++ ++ while(hi2c->XferSize > 0U) ++ { ++ if(hi2c->XferSize <= 3U) ++ { ++ /* One byte */ ++ if(hi2c->XferSize == 1U) ++ { ++ /* Wait until RXNE flag is set */ ++ if(I2C_WaitOnRXNEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_TIMEOUT) ++ { ++ return HAL_TIMEOUT; ++ } ++ else ++ { ++ return HAL_ERROR; ++ } ++ } ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ } ++ /* Two bytes */ ++ else if(hi2c->XferSize == 2U) ++ { ++ /* Wait until BTF flag is set */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BTF, RESET, Timeout, tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ } ++ /* 3 Last bytes */ ++ else ++ { ++ /* Wait until BTF flag is set */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BTF, RESET, Timeout, tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ ++ /* Wait until BTF flag is set */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BTF, RESET, Timeout, tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ } ++ } ++ else ++ { ++ /* Wait until RXNE flag is set */ ++ if(I2C_WaitOnRXNEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_TIMEOUT) ++ { ++ return HAL_TIMEOUT; ++ } ++ else ++ { ++ return HAL_ERROR; ++ } ++ } ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ ++ if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) ++ { ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ } ++ } ++ } ++ ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Transmits in slave mode an amount of data in blocking mode. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @param Timeout Timeout duration ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Slave_Transmit(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout) ++{ ++ uint32_t tickstart = 0x00U; ++ ++ /* Init tickstart for timeout management*/ ++ tickstart = HAL_GetTick(); ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ if((pData == NULL) || (Size == 0U)) ++ { ++ return HAL_ERROR; ++ } ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_TX; ++ hi2c->Mode = HAL_I2C_MODE_SLAVE; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->XferSize = hi2c->XferCount; ++ ++ /* Enable Address Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ /* Wait until ADDR flag is set */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, RESET, Timeout, tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ /* If 10bit addressing mode is selected */ ++ if(hi2c->Init.AddressingMode == I2C_ADDRESSINGMODE_10BIT) ++ { ++ /* Wait until ADDR flag is set */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, RESET, Timeout, tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ } ++ ++ while(hi2c->XferSize > 0U) ++ { ++ /* Wait until TXE flag is set */ ++ if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) ++ { ++ /* Disable Address Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ /* Write data to DR */ ++ hi2c->Instance->DR = (*hi2c->pBuffPtr++); ++ hi2c->XferCount--; ++ hi2c->XferSize--; ++ ++ if((__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) && (hi2c->XferSize != 0U)) ++ { ++ /* Write data to DR */ ++ hi2c->Instance->DR = (*hi2c->pBuffPtr++); ++ hi2c->XferCount--; ++ hi2c->XferSize--; ++ } ++ } ++ ++ /* Wait until AF flag is set */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_AF, RESET, Timeout, tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ ++ /* Clear AF flag */ ++ __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF); ++ ++ /* Disable Address Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Receive in slave mode an amount of data in blocking mode ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @param Timeout Timeout duration ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Slave_Receive(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout) ++{ ++ uint32_t tickstart = 0x00U; ++ ++ /* Init tickstart for timeout management*/ ++ tickstart = HAL_GetTick(); ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ if((pData == NULL) || (Size == 0)) ++ { ++ return HAL_ERROR; ++ } ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_RX; ++ hi2c->Mode = HAL_I2C_MODE_SLAVE; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->XferSize = hi2c->XferCount; ++ ++ /* Enable Address Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ /* Wait until ADDR flag is set */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, RESET, Timeout, tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ while(hi2c->XferSize > 0U) ++ { ++ /* Wait until RXNE flag is set */ ++ if(I2C_WaitOnRXNEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) ++ { ++ /* Disable Address Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_TIMEOUT) ++ { ++ return HAL_TIMEOUT; ++ } ++ else ++ { ++ return HAL_ERROR; ++ } ++ } ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ ++ if((__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) && (Size != 0U)) ++ { ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ } ++ } ++ ++ /* Wait until STOP flag is set */ ++ if(I2C_WaitOnSTOPFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) ++ { ++ /* Disable Address Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ /* Clear STOP flag */ ++ __HAL_I2C_CLEAR_STOPFLAG(hi2c); ++ ++ /* Disable Address Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Transmit in master mode an amount of data in non-blocking mode with Interrupt ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param DevAddress Target device address The device 7 bits address value ++ * in datasheet must be shifted to the left before calling the interface ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size) ++{ ++ __IO uint32_t count = 0U; ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ /* Wait until BUSY flag is reset */ ++ count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); ++ do ++ { ++ if(count-- == 0U) ++ { ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_TX; ++ hi2c->Mode = HAL_I2C_MODE_MASTER; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->XferSize = hi2c->XferCount; ++ hi2c->Devaddress = DevAddress; ++ ++ /* Generate Start */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ /* Note : The I2C interrupts must be enabled after unlocking current process ++ to avoid the risk of I2C interrupt handle execution before current ++ process unlock */ ++ /* Enable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Receive in master mode an amount of data in non-blocking mode with Interrupt ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param DevAddress Target device address The device 7 bits address value ++ * in datasheet must be shifted to the left before calling the interface ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size) ++{ ++ __IO uint32_t count = 0U; ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ /* Wait until BUSY flag is reset */ ++ count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); ++ do ++ { ++ if(count-- == 0U) ++ { ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_RX; ++ hi2c->Mode = HAL_I2C_MODE_MASTER; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->XferSize = hi2c->XferCount; ++ hi2c->Devaddress = DevAddress; ++ ++ /* Enable Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ /* Generate Start */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ /* Note : The I2C interrupts must be enabled after unlocking current process ++ to avoid the risk of I2C interrupt handle execution before current ++ process unlock */ ++ ++ /* Enable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Sequential transmit in master mode an amount of data in non-blocking mode with Interrupt ++ * @note This interface allow to manage repeated start condition when a direction change during transfer ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param DevAddress Target device address The device 7 bits address value ++ * in datasheet must be shifted to the left before calling the interface ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @param XferOptions Options of Transfer, value of @ref I2C_XferOptions_definition ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Master_Sequential_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions) ++{ ++ __IO uint32_t Prev_State = 0x00U; ++ __IO uint32_t count = 0x00U; ++ ++ /* Check the parameters */ ++ assert_param(IS_I2C_TRANSFER_OPTIONS_REQUEST(XferOptions)); ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ /* Check Busy Flag only if FIRST call of Master interface */ ++ if((XferOptions == I2C_FIRST_AND_LAST_FRAME) || (XferOptions == I2C_FIRST_FRAME)) ++ { ++ /* Wait until BUSY flag is reset */ ++ count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); ++ do ++ { ++ if(count-- == 0U) ++ { ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); ++ } ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_TX; ++ hi2c->Mode = HAL_I2C_MODE_MASTER; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = XferOptions; ++ hi2c->XferSize = hi2c->XferCount; ++ hi2c->Devaddress = DevAddress; ++ ++ Prev_State = hi2c->PreviousState; ++ ++ /* Generate Start */ ++ if((Prev_State == I2C_STATE_MASTER_BUSY_RX) || (Prev_State == I2C_STATE_NONE)) ++ { ++ /* Generate Start condition if first transfer */ ++ if((XferOptions == I2C_FIRST_AND_LAST_FRAME) || (XferOptions == I2C_FIRST_FRAME)) ++ { ++ /* Generate Start */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ } ++ else ++ { ++ /* Generate ReStart */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ } ++ } ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ /* Note : The I2C interrupts must be enabled after unlocking current process ++ to avoid the risk of I2C interrupt handle execution before current ++ process unlock */ ++ ++ /* Enable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Sequential receive in master mode an amount of data in non-blocking mode with Interrupt ++ * @note This interface allow to manage repeated start condition when a direction change during transfer ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param DevAddress Target device address The device 7 bits address value ++ * in datasheet must be shifted to the left before calling the interface ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @param XferOptions Options of Transfer, value of @ref I2C_XferOptions_definition ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Master_Sequential_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions) ++{ ++ __IO uint32_t count = 0U; ++ ++ /* Check the parameters */ ++ assert_param(IS_I2C_TRANSFER_OPTIONS_REQUEST(XferOptions)); ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ /* Check Busy Flag only if FIRST call of Master interface */ ++ if((XferOptions == I2C_FIRST_AND_LAST_FRAME) || (XferOptions == I2C_FIRST_FRAME)) ++ { ++ /* Wait until BUSY flag is reset */ ++ count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); ++ do ++ { ++ if(count-- == 0U) ++ { ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); ++ } ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_RX; ++ hi2c->Mode = HAL_I2C_MODE_MASTER; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = XferOptions; ++ hi2c->XferSize = hi2c->XferCount; ++ hi2c->Devaddress = DevAddress; ++ ++ if((hi2c->PreviousState == I2C_STATE_MASTER_BUSY_TX) || (hi2c->PreviousState == I2C_STATE_NONE)) ++ { ++ /* Generate Start condition if first transfer */ ++ if((XferOptions == I2C_FIRST_AND_LAST_FRAME) || (XferOptions == I2C_FIRST_FRAME) || (XferOptions == I2C_NO_OPTION_FRAME)) ++ { ++ /* Enable Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ /* Generate Start */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ } ++ else if(hi2c->PreviousState == I2C_STATE_MASTER_BUSY_TX) ++ { ++ /* Enable Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ /* Generate ReStart */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ } ++ } ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ /* Note : The I2C interrupts must be enabled after unlocking current process ++ to avoid the risk of I2C interrupt handle execution before current ++ process unlock */ ++ ++ /* Enable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Transmit in slave mode an amount of data in non-blocking mode with Interrupt ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Slave_Transmit_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size) ++{ ++ __IO uint32_t count = 0U; ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ if((pData == NULL) || (Size == 0U)) ++ { ++ return HAL_ERROR; ++ } ++ ++ /* Wait until BUSY flag is reset */ ++ count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); ++ do ++ { ++ if(count-- == 0U) ++ { ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_TX; ++ hi2c->Mode = HAL_I2C_MODE_SLAVE; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->XferSize = hi2c->XferCount; ++ ++ /* Enable Address Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ /* Note : The I2C interrupts must be enabled after unlocking current process ++ to avoid the risk of I2C interrupt handle execution before current ++ process unlock */ ++ ++ /* Enable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Receive in slave mode an amount of data in non-blocking mode with Interrupt ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Slave_Receive_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size) ++{ ++ __IO uint32_t count = 0U; ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ if((pData == NULL) || (Size == 0U)) ++ { ++ return HAL_ERROR; ++ } ++ ++ /* Wait until BUSY flag is reset */ ++ count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); ++ do ++ { ++ if(count-- == 0U) ++ { ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_RX; ++ hi2c->Mode = HAL_I2C_MODE_SLAVE; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferSize = Size; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ ++ /* Enable Address Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ /* Note : The I2C interrupts must be enabled after unlocking current process ++ to avoid the risk of I2C interrupt handle execution before current ++ process unlock */ ++ ++ /* Enable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Sequential transmit in slave mode an amount of data in no-blocking mode with Interrupt ++ * @note This interface allow to manage repeated start condition when a direction change during transfer ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @param XferOptions Options of Transfer, value of @ref I2C_XferOptions_definition ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Slave_Sequential_Transmit_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t XferOptions) ++{ ++ /* Check the parameters */ ++ assert_param(IS_I2C_TRANSFER_OPTIONS_REQUEST(XferOptions)); ++ ++ if(hi2c->State == HAL_I2C_STATE_LISTEN) ++ { ++ if((pData == NULL) || (Size == 0U)) ++ { ++ return HAL_ERROR; ++ } ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_TX_LISTEN; ++ hi2c->Mode = HAL_I2C_MODE_SLAVE; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = XferOptions; ++ hi2c->XferSize = hi2c->XferCount; ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ /* Note : The I2C interrupts must be enabled after unlocking current process ++ to avoid the risk of I2C interrupt handle execution before current ++ process unlock */ ++ ++ /* Enable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Sequential receive in slave mode an amount of data in non-blocking mode with Interrupt ++ * @note This interface allow to manage repeated start condition when a direction change during transfer ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @param XferOptions Options of Transfer, value of @ref I2C_XferOptions_definition ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Slave_Sequential_Receive_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t XferOptions) ++{ ++ /* Check the parameters */ ++ assert_param(IS_I2C_TRANSFER_OPTIONS_REQUEST(XferOptions)); ++ ++ if(hi2c->State == HAL_I2C_STATE_LISTEN) ++ { ++ if((pData == NULL) || (Size == 0U)) ++ { ++ return HAL_ERROR; ++ } ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_RX_LISTEN; ++ hi2c->Mode = HAL_I2C_MODE_SLAVE; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = XferOptions; ++ hi2c->XferSize = hi2c->XferCount; ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ /* Note : The I2C interrupts must be enabled after unlocking current process ++ to avoid the risk of I2C interrupt handle execution before current ++ process unlock */ ++ ++ /* Enable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Enable the Address listen mode with Interrupt. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_EnableListen_IT(I2C_HandleTypeDef *hi2c) ++{ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ hi2c->State = HAL_I2C_STATE_LISTEN; ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Enable Address Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ /* Enable EVT and ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_ERR); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Disable the Address listen mode with Interrupt. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_DisableListen_IT(I2C_HandleTypeDef *hi2c) ++{ ++ /* Declaration of tmp to prevent undefined behavior of volatile usage */ ++ uint32_t tmp; ++ ++ /* Disable Address listen mode only if a transfer is not ongoing */ ++ if(hi2c->State == HAL_I2C_STATE_LISTEN) ++ { ++ tmp = (uint32_t)(hi2c->State) & I2C_STATE_MSK; ++ hi2c->PreviousState = tmp | (uint32_t)(hi2c->Mode); ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ /* Disable Address Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ /* Disable EVT and ERR interrupt */ ++ __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_ERR); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Transmit in master mode an amount of data in non-blocking mode with DMA ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param DevAddress Target device address The device 7 bits address value ++ * in datasheet must be shifted to the left before calling the interface ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size) ++{ ++ __IO uint32_t count = 0U; ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ /* Wait until BUSY flag is reset */ ++ count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); ++ do ++ { ++ if(count-- == 0U) ++ { ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_TX; ++ hi2c->Mode = HAL_I2C_MODE_MASTER; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->XferSize = hi2c->XferCount; ++ hi2c->Devaddress = DevAddress; ++ ++ if(hi2c->XferSize > 0U) ++ { ++ /* Set the I2C DMA transfer complete callback */ ++ hi2c->hdmatx->XferCpltCallback = I2C_DMAXferCplt; ++ ++ /* Set the DMA error callback */ ++ hi2c->hdmatx->XferErrorCallback = I2C_DMAError; ++ ++ /* Set the unused DMA callbacks to NULL */ ++ hi2c->hdmatx->XferHalfCpltCallback = NULL; ++ hi2c->hdmatx->XferM1CpltCallback = NULL; ++ hi2c->hdmatx->XferM1HalfCpltCallback = NULL; ++ hi2c->hdmatx->XferAbortCallback = NULL; ++ ++ /* Enable the DMA Stream */ ++ HAL_DMA_Start_IT(hi2c->hdmatx, (uint32_t)hi2c->pBuffPtr, (uint32_t)&hi2c->Instance->DR, hi2c->XferSize); ++ ++ /* Enable Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ /* Generate Start */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ /* Note : The I2C interrupts must be enabled after unlocking current process ++ to avoid the risk of I2C interrupt handle execution before current ++ process unlock */ ++ ++ /* Enable EVT and ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_ERR); ++ ++ /* Enable DMA Request */ ++ hi2c->Instance->CR2 |= I2C_CR2_DMAEN; ++ } ++ else ++ { ++ /* Enable Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ /* Generate Start */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ /* Note : The I2C interrupts must be enabled after unlocking current process ++ to avoid the risk of I2C interrupt handle execution before current ++ process unlock */ ++ ++ /* Enable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ } ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Receive in master mode an amount of data in non-blocking mode with DMA ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param DevAddress Target device address The device 7 bits address value ++ * in datasheet must be shifted to the left before calling the interface ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size) ++{ ++ __IO uint32_t count = 0U; ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ /* Wait until BUSY flag is reset */ ++ count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); ++ do ++ { ++ if(count-- == 0U) ++ { ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_RX; ++ hi2c->Mode = HAL_I2C_MODE_MASTER; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->XferSize = hi2c->XferCount; ++ hi2c->Devaddress = DevAddress; ++ ++ if(hi2c->XferSize > 0U) ++ { ++ /* Set the I2C DMA transfer complete callback */ ++ hi2c->hdmarx->XferCpltCallback = I2C_DMAXferCplt; ++ ++ /* Set the DMA error callback */ ++ hi2c->hdmarx->XferErrorCallback = I2C_DMAError; ++ ++ /* Set the unused DMA callbacks to NULL */ ++ hi2c->hdmarx->XferHalfCpltCallback = NULL; ++ hi2c->hdmarx->XferM1CpltCallback = NULL; ++ hi2c->hdmarx->XferM1HalfCpltCallback = NULL; ++ hi2c->hdmarx->XferAbortCallback = NULL; ++ ++ /* Enable the DMA Stream */ ++ HAL_DMA_Start_IT(hi2c->hdmarx, (uint32_t)&hi2c->Instance->DR, (uint32_t)hi2c->pBuffPtr, hi2c->XferSize); ++ ++ /* Enable Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ /* Generate Start */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ /* Note : The I2C interrupts must be enabled after unlocking current process ++ to avoid the risk of I2C interrupt handle execution before current ++ process unlock */ ++ ++ /* Enable EVT and ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_ERR); ++ ++ /* Enable DMA Request */ ++ hi2c->Instance->CR2 |= I2C_CR2_DMAEN; ++ } ++ else ++ { ++ /* Enable Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ /* Generate Start */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ /* Note : The I2C interrupts must be enabled after unlocking current process ++ to avoid the risk of I2C interrupt handle execution before current ++ process unlock */ ++ ++ /* Enable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ } ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Abort a master I2C process communication with Interrupt. ++ * @note This abort can be called only if state is ready ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param DevAddress Target device address The device 7 bits address value ++ * in datasheet must be shifted to the left before calling the interface ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Master_Abort_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress) ++{ ++ /* Prevent unused argument(s) compilation warning */ ++ UNUSED(DevAddress); ++ ++ /* Abort Master transfer during Receive or Transmit process */ ++ if(hi2c->Mode == HAL_I2C_MODE_MASTER) ++ { ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State = HAL_I2C_STATE_ABORT; ++ ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ ++ hi2c->XferCount = 0U; ++ ++ /* Disable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ /* Call the corresponding callback to inform upper layer of End of Transfer */ ++ I2C_ITError(hi2c); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ /* Wrong usage of abort function */ ++ /* This function should be used only in case of abort monitored by master device */ ++ return HAL_ERROR; ++ } ++} ++ ++/** ++ * @brief Transmit in slave mode an amount of data in non-blocking mode with DMA ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Slave_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size) ++{ ++ __IO uint32_t count = 0U; ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ if((pData == NULL) || (Size == 0U)) ++ { ++ return HAL_ERROR; ++ } ++ ++ /* Wait until BUSY flag is reset */ ++ count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); ++ do ++ { ++ if(count-- == 0U) ++ { ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_TX; ++ hi2c->Mode = HAL_I2C_MODE_SLAVE; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->XferSize = hi2c->XferCount; ++ ++ /* Set the I2C DMA transfer complete callback */ ++ hi2c->hdmatx->XferCpltCallback = I2C_DMAXferCplt; ++ ++ /* Set the DMA error callback */ ++ hi2c->hdmatx->XferErrorCallback = I2C_DMAError; ++ ++ /* Set the unused DMA callbacks to NULL */ ++ hi2c->hdmatx->XferHalfCpltCallback = NULL; ++ hi2c->hdmatx->XferM1CpltCallback = NULL; ++ hi2c->hdmatx->XferM1HalfCpltCallback = NULL; ++ hi2c->hdmatx->XferAbortCallback = NULL; ++ ++ /* Enable the DMA Stream */ ++ HAL_DMA_Start_IT(hi2c->hdmatx, (uint32_t)hi2c->pBuffPtr, (uint32_t)&hi2c->Instance->DR, hi2c->XferSize); ++ ++ /* Enable Address Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ /* Note : The I2C interrupts must be enabled after unlocking current process ++ to avoid the risk of I2C interrupt handle execution before current ++ process unlock */ ++ /* Enable EVT and ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_ERR); ++ ++ /* Enable DMA Request */ ++ hi2c->Instance->CR2 |= I2C_CR2_DMAEN; ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Receive in slave mode an amount of data in non-blocking mode with DMA ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Slave_Receive_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size) ++{ ++ __IO uint32_t count = 0U; ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ if((pData == NULL) || (Size == 0U)) ++ { ++ return HAL_ERROR; ++ } ++ ++ /* Wait until BUSY flag is reset */ ++ count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); ++ do ++ { ++ if(count-- == 0U) ++ { ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_RX; ++ hi2c->Mode = HAL_I2C_MODE_SLAVE; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->XferSize = hi2c->XferCount; ++ ++ /* Set the I2C DMA transfer complete callback */ ++ hi2c->hdmarx->XferCpltCallback = I2C_DMAXferCplt; ++ ++ /* Set the DMA error callback */ ++ hi2c->hdmarx->XferErrorCallback = I2C_DMAError; ++ ++ /* Set the unused DMA callbacks to NULL */ ++ hi2c->hdmarx->XferHalfCpltCallback = NULL; ++ hi2c->hdmarx->XferM1CpltCallback = NULL; ++ hi2c->hdmarx->XferM1HalfCpltCallback = NULL; ++ hi2c->hdmarx->XferAbortCallback = NULL; ++ ++ /* Enable the DMA Stream */ ++ HAL_DMA_Start_IT(hi2c->hdmarx, (uint32_t)&hi2c->Instance->DR, (uint32_t)hi2c->pBuffPtr, hi2c->XferSize); ++ ++ /* Enable Address Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ /* Note : The I2C interrupts must be enabled after unlocking current process ++ to avoid the risk of I2C interrupt handle execution before current ++ process unlock */ ++ /* Enable EVT and ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_ERR); ++ ++ /* Enable DMA Request */ ++ hi2c->Instance->CR2 |= I2C_CR2_DMAEN; ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++/** ++ * @brief Write an amount of data in blocking mode to a specific memory address ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param DevAddress Target device address ++ * @param MemAddress Internal memory address ++ * @param MemAddSize Size of internal memory address ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @param Timeout Timeout duration ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout) ++{ ++ uint32_t tickstart = 0x00U; ++ ++ /* Init tickstart for timeout management*/ ++ tickstart = HAL_GetTick(); ++ ++ /* Check the parameters */ ++ assert_param(IS_I2C_MEMADD_SIZE(MemAddSize)); ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ /* Wait until BUSY flag is reset */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK) ++ { ++ return HAL_BUSY; ++ } ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_TX; ++ hi2c->Mode = HAL_I2C_MODE_MEM; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->XferSize = hi2c->XferCount; ++ ++ /* Send Slave Address and Memory Address */ ++ if(I2C_RequestMemoryWrite(hi2c, DevAddress, MemAddress, MemAddSize, Timeout, tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ return HAL_ERROR; ++ } ++ else ++ { ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ while(hi2c->XferSize > 0U) ++ { ++ /* Wait until TXE flag is set */ ++ if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ /* Write data to DR */ ++ hi2c->Instance->DR = (*hi2c->pBuffPtr++); ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ ++ if((__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) && (hi2c->XferSize != 0U)) ++ { ++ /* Write data to DR */ ++ hi2c->Instance->DR = (*hi2c->pBuffPtr++); ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ } ++ } ++ ++ /* Wait until BTF flag is set */ ++ if(I2C_WaitOnBTFFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Read an amount of data in blocking mode from a specific memory address ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param DevAddress Target device address ++ * @param MemAddress Internal memory address ++ * @param MemAddSize Size of internal memory address ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @param Timeout Timeout duration ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout) ++{ ++ uint32_t tickstart = 0x00U; ++ ++ /* Init tickstart for timeout management*/ ++ tickstart = HAL_GetTick(); ++ ++ /* Check the parameters */ ++ assert_param(IS_I2C_MEMADD_SIZE(MemAddSize)); ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ /* Wait until BUSY flag is reset */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK) ++ { ++ return HAL_BUSY; ++ } ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_RX; ++ hi2c->Mode = HAL_I2C_MODE_MEM; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->XferSize = hi2c->XferCount; ++ ++ /* Send Slave Address and Memory Address */ ++ if(I2C_RequestMemoryRead(hi2c, DevAddress, MemAddress, MemAddSize, Timeout, tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ return HAL_ERROR; ++ } ++ else ++ { ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ if(hi2c->XferSize == 0U) ++ { ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ } ++ else if(hi2c->XferSize == 1U) ++ { ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ } ++ else if(hi2c->XferSize == 2U) ++ { ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ /* Enable Pos */ ++ hi2c->Instance->CR1 |= I2C_CR1_POS; ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ } ++ else ++ { ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ } ++ ++ while(hi2c->XferSize > 0U) ++ { ++ if(hi2c->XferSize <= 3U) ++ { ++ /* One byte */ ++ if(hi2c->XferSize== 1U) ++ { ++ /* Wait until RXNE flag is set */ ++ if(I2C_WaitOnRXNEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_TIMEOUT) ++ { ++ return HAL_TIMEOUT; ++ } ++ else ++ { ++ return HAL_ERROR; ++ } ++ } ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ } ++ /* Two bytes */ ++ else if(hi2c->XferSize == 2U) ++ { ++ /* Wait until BTF flag is set */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BTF, RESET, Timeout, tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ } ++ /* 3 Last bytes */ ++ else ++ { ++ /* Wait until BTF flag is set */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BTF, RESET, Timeout, tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ ++ /* Wait until BTF flag is set */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BTF, RESET, Timeout, tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ } ++ } ++ else ++ { ++ /* Wait until RXNE flag is set */ ++ if(I2C_WaitOnRXNEFlagUntilTimeout(hi2c, Timeout, tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_TIMEOUT) ++ { ++ return HAL_TIMEOUT; ++ } ++ else ++ { ++ return HAL_ERROR; ++ } ++ } ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ ++ if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) ++ { ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferSize--; ++ hi2c->XferCount--; ++ } ++ } ++ } ++ ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Write an amount of data in non-blocking mode with Interrupt to a specific memory address ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param DevAddress Target device address ++ * @param MemAddress Internal memory address ++ * @param MemAddSize Size of internal memory address ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Mem_Write_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size) ++{ ++ __IO uint32_t count = 0U; ++ ++ /* Check the parameters */ ++ assert_param(IS_I2C_MEMADD_SIZE(MemAddSize)); ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ /* Wait until BUSY flag is reset */ ++ count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); ++ do ++ { ++ if(count-- == 0U) ++ { ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_TX; ++ hi2c->Mode = HAL_I2C_MODE_MEM; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferSize = Size; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->Devaddress = DevAddress; ++ hi2c->Memaddress = MemAddress; ++ hi2c->MemaddSize = MemAddSize; ++ hi2c->EventCount = 0U; ++ ++ /* Generate Start */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ /* Note : The I2C interrupts must be enabled after unlocking current process ++ to avoid the risk of I2C interrupt handle execution before current ++ process unlock */ ++ ++ /* Enable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Read an amount of data in non-blocking mode with Interrupt from a specific memory address ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param DevAddress Target device address ++ * @param MemAddress Internal memory address ++ * @param MemAddSize Size of internal memory address ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Mem_Read_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size) ++{ ++ __IO uint32_t count = 0U; ++ ++ /* Check the parameters */ ++ assert_param(IS_I2C_MEMADD_SIZE(MemAddSize)); ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ /* Wait until BUSY flag is reset */ ++ count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); ++ do ++ { ++ if(count-- == 0U) ++ { ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_RX; ++ hi2c->Mode = HAL_I2C_MODE_MEM; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferSize = Size; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->Devaddress = DevAddress; ++ hi2c->Memaddress = MemAddress; ++ hi2c->MemaddSize = MemAddSize; ++ hi2c->EventCount = 0U; ++ ++ /* Enable Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ /* Generate Start */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ if(hi2c->XferSize > 0U) ++ { ++ /* Note : The I2C interrupts must be enabled after unlocking current process ++ to avoid the risk of I2C interrupt handle execution before current ++ process unlock */ ++ ++ /* Enable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ } ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Write an amount of data in non-blocking mode with DMA to a specific memory address ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param DevAddress Target device address ++ * @param MemAddress Internal memory address ++ * @param MemAddSize Size of internal memory address ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be sent ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Mem_Write_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size) ++{ ++ __IO uint32_t count = 0U; ++ ++ uint32_t tickstart = 0x00U; ++ ++ /* Init tickstart for timeout management*/ ++ tickstart = HAL_GetTick(); ++ ++ /* Check the parameters */ ++ assert_param(IS_I2C_MEMADD_SIZE(MemAddSize)); ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ /* Wait until BUSY flag is reset */ ++ count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); ++ do ++ { ++ if(count-- == 0U) ++ { ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_TX; ++ hi2c->Mode = HAL_I2C_MODE_MEM; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferSize = Size; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ ++ if(hi2c->XferSize > 0U) ++ { ++ /* Set the I2C DMA transfer complete callback */ ++ hi2c->hdmatx->XferCpltCallback = I2C_DMAXferCplt; ++ ++ /* Set the DMA error callback */ ++ hi2c->hdmatx->XferErrorCallback = I2C_DMAError; ++ ++ /* Set the unused DMA callbacks to NULL */ ++ hi2c->hdmatx->XferHalfCpltCallback = NULL; ++ hi2c->hdmatx->XferM1CpltCallback = NULL; ++ hi2c->hdmatx->XferM1HalfCpltCallback = NULL; ++ hi2c->hdmatx->XferAbortCallback = NULL; ++ ++ /* Enable the DMA Stream */ ++ HAL_DMA_Start_IT(hi2c->hdmatx, (uint32_t)hi2c->pBuffPtr, (uint32_t)&hi2c->Instance->DR, hi2c->XferSize); ++ ++ /* Send Slave Address and Memory Address */ ++ if(I2C_RequestMemoryWrite(hi2c, DevAddress, MemAddress, MemAddSize, I2C_TIMEOUT_FLAG, tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ return HAL_ERROR; ++ } ++ else ++ { ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ /* Note : The I2C interrupts must be enabled after unlocking current process ++ to avoid the risk of I2C interrupt handle execution before current ++ process unlock */ ++ /* Enable ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_ERR); ++ ++ /* Enable DMA Request */ ++ hi2c->Instance->CR2 |= I2C_CR2_DMAEN; ++ } ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Reads an amount of data in non-blocking mode with DMA from a specific memory address. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param DevAddress Target device address ++ * @param MemAddress Internal memory address ++ * @param MemAddSize Size of internal memory address ++ * @param pData Pointer to data buffer ++ * @param Size Amount of data to be read ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_Mem_Read_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size) ++{ ++ uint32_t tickstart = 0x00U; ++ __IO uint32_t count = 0U; ++ ++ /* Init tickstart for timeout management*/ ++ tickstart = HAL_GetTick(); ++ ++ /* Check the parameters */ ++ assert_param(IS_I2C_MEMADD_SIZE(MemAddSize)); ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ /* Wait until BUSY flag is reset */ ++ count = I2C_TIMEOUT_BUSY_FLAG * (SystemCoreClock /25U /1000U); ++ do ++ { ++ if(count-- == 0U) ++ { ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BUSY) != RESET); ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY_RX; ++ hi2c->Mode = HAL_I2C_MODE_MEM; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Prepare transfer parameters */ ++ hi2c->pBuffPtr = pData; ++ hi2c->XferCount = Size; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->XferSize = hi2c->XferCount; ++ ++ if(hi2c->XferSize > 0U) ++ { ++ /* Set the I2C DMA transfer complete callback */ ++ hi2c->hdmarx->XferCpltCallback = I2C_DMAXferCplt; ++ ++ /* Set the DMA error callback */ ++ hi2c->hdmarx->XferErrorCallback = I2C_DMAError; ++ ++ /* Set the unused DMA callbacks to NULL */ ++ hi2c->hdmarx->XferHalfCpltCallback = NULL; ++ hi2c->hdmarx->XferM1CpltCallback = NULL; ++ hi2c->hdmarx->XferM1HalfCpltCallback = NULL; ++ hi2c->hdmarx->XferAbortCallback = NULL; ++ ++ /* Enable the DMA Stream */ ++ HAL_DMA_Start_IT(hi2c->hdmarx, (uint32_t)&hi2c->Instance->DR, (uint32_t)hi2c->pBuffPtr, hi2c->XferSize); ++ ++ /* Send Slave Address and Memory Address */ ++ if(I2C_RequestMemoryRead(hi2c, DevAddress, MemAddress, MemAddSize, I2C_TIMEOUT_FLAG, tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ return HAL_ERROR; ++ } ++ else ++ { ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ if(Size == 1U) ++ { ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ } ++ else ++ { ++ /* Enable Last DMA bit */ ++ hi2c->Instance->CR2 |= I2C_CR2_LAST; ++ } ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ /* Note : The I2C interrupts must be enabled after unlocking current process ++ to avoid the risk of I2C interrupt handle execution before current ++ process unlock */ ++ /* Enable ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_ERR); ++ ++ /* Enable DMA Request */ ++ hi2c->Instance->CR2 |= I2C_CR2_DMAEN; ++ } ++ else ++ { ++ /* Send Slave Address and Memory Address */ ++ if(I2C_RequestMemoryRead(hi2c, DevAddress, MemAddress, MemAddSize, I2C_TIMEOUT_FLAG, tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ return HAL_ERROR; ++ } ++ else ++ { ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ ++ hi2c->State = HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ } ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Checks if target device is ready for communication. ++ * @note This function is used with Memory devices ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param DevAddress Target device address ++ * @param Trials Number of trials ++ * @param Timeout Timeout duration ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout) ++{ ++ uint32_t tickstart = 0U, tmp1 = 0U, tmp2 = 0U, tmp3 = 0U, I2C_Trials = 1U; ++ ++ /* Get tick */ ++ tickstart = HAL_GetTick(); ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ /* Wait until BUSY flag is reset */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK) ++ { ++ return HAL_BUSY; ++ } ++ ++ /* Process Locked */ ++ __HAL_LOCK(hi2c); ++ ++ /* Check if the I2C is already enabled */ ++ if((hi2c->Instance->CR1 & I2C_CR1_PE) != I2C_CR1_PE) ++ { ++ /* Enable I2C peripheral */ ++ __HAL_I2C_ENABLE(hi2c); ++ } ++ ++ /* Disable Pos */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ hi2c->State = HAL_I2C_STATE_BUSY; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ ++ do ++ { ++ /* Generate Start */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ ++ /* Wait until SB flag is set */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_SB, RESET, Timeout, tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ ++ /* Send slave address */ ++ hi2c->Instance->DR = I2C_7BIT_ADD_WRITE(DevAddress); ++ ++ /* Wait until ADDR or AF flag are set */ ++ /* Get tick */ ++ tickstart = HAL_GetTick(); ++ ++ tmp1 = __HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_ADDR); ++ tmp2 = __HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_AF); ++ tmp3 = hi2c->State; ++ while((tmp1 == RESET) && (tmp2 == RESET) && (tmp3 != HAL_I2C_STATE_TIMEOUT)) ++ { ++ if((Timeout == 0U)||((HAL_GetTick() - tickstart ) > Timeout)) ++ { ++ hi2c->State = HAL_I2C_STATE_TIMEOUT; ++ } ++ tmp1 = __HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_ADDR); ++ tmp2 = __HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_AF); ++ tmp3 = hi2c->State; ++ } ++ ++ hi2c->State = HAL_I2C_STATE_READY; ++ ++ /* Check if the ADDR flag has been set */ ++ if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_ADDR) == SET) ++ { ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ ++ /* Clear ADDR Flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ /* Wait until BUSY flag is reset */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ ++ hi2c->State = HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_OK; ++ } ++ else ++ { ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ ++ /* Clear AF Flag */ ++ __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF); ++ ++ /* Wait until BUSY flag is reset */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_BUSY, SET, I2C_TIMEOUT_BUSY_FLAG, tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ }while(I2C_Trials++ < Trials); ++ ++ hi2c->State = HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief This function handles I2C event interrupt request. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @retval None ++ */ ++void HAL_I2C_EV_IRQHandler(I2C_HandleTypeDef *hi2c) ++{ ++ uint32_t sr2itflags = READ_REG(hi2c->Instance->SR2); ++ uint32_t sr1itflags = READ_REG(hi2c->Instance->SR1); ++ uint32_t itsources = READ_REG(hi2c->Instance->CR2); ++ ++ uint32_t CurrentMode = hi2c->Mode; ++ ++ /* Master or Memory mode selected */ ++ if((CurrentMode == HAL_I2C_MODE_MASTER) || (CurrentMode == HAL_I2C_MODE_MEM)) ++ { ++ /* SB Set ----------------------------------------------------------------*/ ++ if(((sr1itflags & I2C_FLAG_SB) != RESET) && ((itsources & I2C_IT_EVT) != RESET)) ++ { ++ I2C_Master_SB(hi2c); ++ } ++ /* ADD10 Set -------------------------------------------------------------*/ ++ else if(((sr1itflags & I2C_FLAG_ADD10) != RESET) && ((itsources & I2C_IT_EVT) != RESET)) ++ { ++ I2C_Master_ADD10(hi2c); ++ } ++ /* ADDR Set --------------------------------------------------------------*/ ++ else if(((sr1itflags & I2C_FLAG_ADDR) != RESET) && ((itsources & I2C_IT_EVT) != RESET)) ++ { ++ I2C_Master_ADDR(hi2c); ++ } ++ ++ /* I2C in mode Transmitter -----------------------------------------------*/ ++ if((sr2itflags & I2C_FLAG_TRA) != RESET) ++ { ++ /* TXE set and BTF reset -----------------------------------------------*/ ++ if(((sr1itflags & I2C_FLAG_TXE) != RESET) && ((itsources & I2C_IT_BUF) != RESET) && ((sr1itflags & I2C_FLAG_BTF) == RESET)) ++ { ++ I2C_MasterTransmit_TXE(hi2c); ++ } ++ /* BTF set -------------------------------------------------------------*/ ++ else if(((sr1itflags & I2C_FLAG_BTF) != RESET) && ((itsources & I2C_IT_EVT) != RESET)) ++ { ++ I2C_MasterTransmit_BTF(hi2c); ++ } ++ } ++ /* I2C in mode Receiver --------------------------------------------------*/ ++ else ++ { ++ /* RXNE set and BTF reset -----------------------------------------------*/ ++ if(((sr1itflags & I2C_FLAG_RXNE) != RESET) && ((itsources & I2C_IT_BUF) != RESET) && ((sr1itflags & I2C_FLAG_BTF) == RESET)) ++ { ++ I2C_MasterReceive_RXNE(hi2c); ++ } ++ /* BTF set -------------------------------------------------------------*/ ++ else if(((sr1itflags & I2C_FLAG_BTF) != RESET) && ((itsources & I2C_IT_EVT) != RESET)) ++ { ++ I2C_MasterReceive_BTF(hi2c); ++ } ++ } ++ } ++ /* Slave mode selected */ ++ else ++ { ++ /* ADDR set --------------------------------------------------------------*/ ++ if(((sr1itflags & I2C_FLAG_ADDR) != RESET) && ((itsources & I2C_IT_EVT) != RESET)) ++ { ++ I2C_Slave_ADDR(hi2c); ++ } ++ /* STOPF set --------------------------------------------------------------*/ ++ else if(((sr1itflags & I2C_FLAG_STOPF) != RESET) && ((itsources & I2C_IT_EVT) != RESET)) ++ { ++ I2C_Slave_STOPF(hi2c); ++ } ++ /* I2C in mode Transmitter -----------------------------------------------*/ ++ else if((sr2itflags & I2C_FLAG_TRA) != RESET) ++ { ++ /* TXE set and BTF reset -----------------------------------------------*/ ++ if(((sr1itflags & I2C_FLAG_TXE) != RESET) && ((itsources & I2C_IT_BUF) != RESET) && ((sr1itflags & I2C_FLAG_BTF) == RESET)) ++ { ++ I2C_SlaveTransmit_TXE(hi2c); ++ } ++ /* BTF set -------------------------------------------------------------*/ ++ else if(((sr1itflags & I2C_FLAG_BTF) != RESET) && ((itsources & I2C_IT_EVT) != RESET)) ++ { ++ I2C_SlaveTransmit_BTF(hi2c); ++ } ++ } ++ /* I2C in mode Receiver --------------------------------------------------*/ ++ else ++ { ++ /* RXNE set and BTF reset ----------------------------------------------*/ ++ if(((sr1itflags & I2C_FLAG_RXNE) != RESET) && ((itsources & I2C_IT_BUF) != RESET) && ((sr1itflags & I2C_FLAG_BTF) == RESET)) ++ { ++ I2C_SlaveReceive_RXNE(hi2c); ++ } ++ /* BTF set -------------------------------------------------------------*/ ++ else if(((sr1itflags & I2C_FLAG_BTF) != RESET) && ((itsources & I2C_IT_EVT) != RESET)) ++ { ++ I2C_SlaveReceive_BTF(hi2c); ++ } ++ } ++ } ++} ++ ++/** ++ * @brief This function handles I2C error interrupt request. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @retval None ++ */ ++void HAL_I2C_ER_IRQHandler(I2C_HandleTypeDef *hi2c) ++{ ++ uint32_t tmp1 = 0U, tmp2 = 0U, tmp3 = 0U, tmp4 = 0U; ++ uint32_t sr1itflags = READ_REG(hi2c->Instance->SR1); ++ uint32_t itsources = READ_REG(hi2c->Instance->CR2); ++ ++ /* I2C Bus error interrupt occurred ----------------------------------------*/ ++ if(((sr1itflags & I2C_FLAG_BERR) != RESET) && ((itsources & I2C_IT_ERR) != RESET)) ++ { ++ hi2c->ErrorCode |= HAL_I2C_ERROR_BERR; ++ ++ /* Clear BERR flag */ ++ __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_BERR); ++ } ++ ++ /* I2C Arbitration Loss error interrupt occurred ---------------------------*/ ++ if(((sr1itflags & I2C_FLAG_ARLO) != RESET) && ((itsources & I2C_IT_ERR) != RESET)) ++ { ++ hi2c->ErrorCode |= HAL_I2C_ERROR_ARLO; ++ ++ /* Clear ARLO flag */ ++ __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_ARLO); ++ } ++ ++ /* I2C Acknowledge failure error interrupt occurred ------------------------*/ ++ if(((sr1itflags & I2C_FLAG_AF) != RESET) && ((itsources & I2C_IT_ERR) != RESET)) ++ { ++ tmp1 = hi2c->Mode; ++ tmp2 = hi2c->XferCount; ++ tmp3 = hi2c->State; ++ tmp4 = hi2c->PreviousState; ++ if((tmp1 == HAL_I2C_MODE_SLAVE) && (tmp2 == 0U) && \ ++ ((tmp3 == HAL_I2C_STATE_BUSY_TX) || (tmp3 == HAL_I2C_STATE_BUSY_TX_LISTEN) || \ ++ ((tmp3 == HAL_I2C_STATE_LISTEN) && (tmp4 == I2C_STATE_SLAVE_BUSY_TX)))) ++ { ++ I2C_Slave_AF(hi2c); ++ } ++ else ++ { ++ hi2c->ErrorCode |= HAL_I2C_ERROR_AF; ++ ++ /* Do not generate a STOP in case of Slave receive non acknowledge during transfer (mean not at the end of transfer) */ ++ if(hi2c->Mode == HAL_I2C_MODE_MASTER) ++ { ++ /* Generate Stop */ ++ SET_BIT(hi2c->Instance->CR1,I2C_CR1_STOP); ++ } ++ ++ /* Clear AF flag */ ++ __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF); ++ } ++ } ++ ++ /* I2C Over-Run/Under-Run interrupt occurred -------------------------------*/ ++ if(((sr1itflags & I2C_FLAG_OVR) != RESET) && ((itsources & I2C_IT_ERR) != RESET)) ++ { ++ hi2c->ErrorCode |= HAL_I2C_ERROR_OVR; ++ /* Clear OVR flag */ ++ __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_OVR); ++ } ++ ++ /* Call the Error Callback in case of Error detected -----------------------*/ ++ if(hi2c->ErrorCode != HAL_I2C_ERROR_NONE) ++ { ++ I2C_ITError(hi2c); ++ } ++} ++ ++/** ++ * @brief Master Tx Transfer completed callback. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @retval None ++ */ ++__weak void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c) ++{ ++ /* Prevent unused argument(s) compilation warning */ ++ UNUSED(hi2c); ++ ++ /* NOTE : This function should not be modified, when the callback is needed, ++ the HAL_I2C_MasterTxCpltCallback can be implemented in the user file ++ */ ++} ++ ++/** ++ * @brief Master Rx Transfer completed callback. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @retval None ++ */ ++__weak void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c) ++{ ++ /* Prevent unused argument(s) compilation warning */ ++ UNUSED(hi2c); ++ ++ /* NOTE : This function should not be modified, when the callback is needed, ++ the HAL_I2C_MasterRxCpltCallback can be implemented in the user file ++ */ ++} ++ ++/** @brief Slave Tx Transfer completed callback. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @retval None ++ */ ++__weak void HAL_I2C_SlaveTxCpltCallback(I2C_HandleTypeDef *hi2c) ++{ ++ /* Prevent unused argument(s) compilation warning */ ++ UNUSED(hi2c); ++ ++ /* NOTE : This function should not be modified, when the callback is needed, ++ the HAL_I2C_SlaveTxCpltCallback can be implemented in the user file ++ */ ++} ++ ++/** ++ * @brief Slave Rx Transfer completed callback. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @retval None ++ */ ++__weak void HAL_I2C_SlaveRxCpltCallback(I2C_HandleTypeDef *hi2c) ++{ ++ /* Prevent unused argument(s) compilation warning */ ++ UNUSED(hi2c); ++ ++ /* NOTE : This function should not be modified, when the callback is needed, ++ the HAL_I2C_SlaveRxCpltCallback can be implemented in the user file ++ */ ++} ++ ++/** ++ * @brief Slave Address Match callback. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param TransferDirection Master request Transfer Direction (Write/Read), value of @ref I2C_XferOptions_definition ++ * @param AddrMatchCode Address Match Code ++ * @retval None ++ */ ++__weak void HAL_I2C_AddrCallback(I2C_HandleTypeDef *hi2c, uint8_t TransferDirection, uint16_t AddrMatchCode) ++{ ++ /* Prevent unused argument(s) compilation warning */ ++ UNUSED(hi2c); ++ UNUSED(TransferDirection); ++ UNUSED(AddrMatchCode); ++ ++ /* NOTE : This function should not be modified, when the callback is needed, ++ the HAL_I2C_AddrCallback can be implemented in the user file ++ */ ++} ++ ++/** ++ * @brief Listen Complete callback. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @retval None ++ */ ++__weak void HAL_I2C_ListenCpltCallback(I2C_HandleTypeDef *hi2c) ++{ ++ /* Prevent unused argument(s) compilation warning */ ++ UNUSED(hi2c); ++ ++ /* NOTE : This function should not be modified, when the callback is needed, ++ the HAL_I2C_ListenCpltCallback can be implemented in the user file ++ */ ++} ++ ++/** ++ * @brief Memory Tx Transfer completed callback. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @retval None ++ */ ++__weak void HAL_I2C_MemTxCpltCallback(I2C_HandleTypeDef *hi2c) ++{ ++ /* Prevent unused argument(s) compilation warning */ ++ UNUSED(hi2c); ++ ++ /* NOTE : This function should not be modified, when the callback is needed, ++ the HAL_I2C_MemTxCpltCallback can be implemented in the user file ++ */ ++} ++ ++/** ++ * @brief Memory Rx Transfer completed callback. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @retval None ++ */ ++__weak void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c) ++{ ++ /* Prevent unused argument(s) compilation warning */ ++ UNUSED(hi2c); ++ ++ /* NOTE : This function should not be modified, when the callback is needed, ++ the HAL_I2C_MemRxCpltCallback can be implemented in the user file ++ */ ++} ++ ++/** ++ * @brief I2C error callback. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @retval None ++ */ ++__weak void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c) ++{ ++ /* Prevent unused argument(s) compilation warning */ ++ UNUSED(hi2c); ++ ++ /* NOTE : This function should not be modified, when the callback is needed, ++ the HAL_I2C_ErrorCallback can be implemented in the user file ++ */ ++} ++ ++/** ++ * @brief I2C abort callback. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @retval None ++ */ ++__weak void HAL_I2C_AbortCpltCallback(I2C_HandleTypeDef *hi2c) ++{ ++ /* Prevent unused argument(s) compilation warning */ ++ UNUSED(hi2c); ++ ++ /* NOTE : This function should not be modified, when the callback is needed, ++ the HAL_I2C_AbortCpltCallback could be implemented in the user file ++ */ ++} ++ ++/** ++ * @} ++ */ ++ ++/** @defgroup I2C_Exported_Functions_Group3 Peripheral State, Mode and Error functions ++ * @brief Peripheral State and Errors functions ++ * ++@verbatim ++ =============================================================================== ++ ##### Peripheral State, Mode and Error functions ##### ++ =============================================================================== ++ [..] ++ This subsection permits to get in run-time the status of the peripheral ++ and the data flow. ++ ++@endverbatim ++ * @{ ++ */ ++ ++/** ++ * @brief Return the I2C handle state. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @retval HAL state ++ */ ++HAL_I2C_StateTypeDef HAL_I2C_GetState(I2C_HandleTypeDef *hi2c) ++{ ++ /* Return I2C handle state */ ++ return hi2c->State; ++} ++ ++/** ++ * @brief Return the I2C Master, Slave, Memory or no mode. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval HAL mode ++ */ ++HAL_I2C_ModeTypeDef HAL_I2C_GetMode(I2C_HandleTypeDef *hi2c) ++{ ++ return hi2c->Mode; ++} ++ ++/** ++ * @brief Return the I2C error code ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @retval I2C Error Code ++ */ ++uint32_t HAL_I2C_GetError(I2C_HandleTypeDef *hi2c) ++{ ++ return hi2c->ErrorCode; ++} ++ ++/** ++ * @} ++ */ ++ ++/** ++ * @brief Handle TXE flag for Master ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_MasterTransmit_TXE(I2C_HandleTypeDef *hi2c) ++{ ++ /* Declaration of temporary variables to prevent undefined behavior of volatile usage */ ++ uint32_t CurrentState = hi2c->State; ++ uint32_t CurrentMode = hi2c->Mode; ++ uint32_t CurrentXferOptions = hi2c->XferOptions; ++ ++ if((hi2c->XferSize == 0U) && (CurrentState == HAL_I2C_STATE_BUSY_TX)) ++ { ++ /* Call TxCpltCallback() directly if no stop mode is set */ ++ if((CurrentXferOptions != I2C_FIRST_AND_LAST_FRAME) && (CurrentXferOptions != I2C_LAST_FRAME) && (CurrentXferOptions != I2C_NO_OPTION_FRAME)) ++ { ++ __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ ++ hi2c->PreviousState = I2C_STATE_MASTER_BUSY_TX; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ hi2c->State = HAL_I2C_STATE_READY; ++ ++ HAL_I2C_MasterTxCpltCallback(hi2c); ++ } ++ else /* Generate Stop condition then Call TxCpltCallback() */ ++ { ++ /* Disable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State = HAL_I2C_STATE_READY; ++ ++ if(hi2c->Mode == HAL_I2C_MODE_MEM) ++ { ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ HAL_I2C_MemTxCpltCallback(hi2c); ++ } ++ else ++ { ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ HAL_I2C_MasterTxCpltCallback(hi2c); ++ } ++ } ++ } ++ else if((CurrentState == HAL_I2C_STATE_BUSY_TX) || \ ++ ((CurrentMode == HAL_I2C_MODE_MEM) && (CurrentState == HAL_I2C_STATE_BUSY_RX))) ++ { ++ if(hi2c->XferCount == 0U) ++ { ++ /* Disable BUF interrupt */ ++ __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_BUF); ++ } ++ else ++ { ++ if(hi2c->Mode == HAL_I2C_MODE_MEM) ++ { ++ if(hi2c->EventCount == 0) ++ { ++ /* If Memory address size is 8Bit */ ++ if(hi2c->MemaddSize == I2C_MEMADD_SIZE_8BIT) ++ { ++ /* Send Memory Address */ ++ hi2c->Instance->DR = I2C_MEM_ADD_LSB(hi2c->Memaddress); ++ ++ hi2c->EventCount += 2; ++ } ++ /* If Memory address size is 16Bit */ ++ else ++ { ++ /* Send MSB of Memory Address */ ++ hi2c->Instance->DR = I2C_MEM_ADD_MSB(hi2c->Memaddress); ++ ++ hi2c->EventCount++; ++ } ++ } ++ else if(hi2c->EventCount == 1) ++ { ++ /* Send LSB of Memory Address */ ++ hi2c->Instance->DR = I2C_MEM_ADD_LSB(hi2c->Memaddress); ++ ++ hi2c->EventCount++; ++ } ++ else if(hi2c->EventCount == 2) ++ { ++ if(hi2c->State == HAL_I2C_STATE_BUSY_RX) ++ { ++ /* Generate Restart */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ } ++ else if(hi2c->State == HAL_I2C_STATE_BUSY_TX) ++ { ++ /* Write data to DR */ ++ hi2c->Instance->DR = (*hi2c->pBuffPtr++); ++ hi2c->XferCount--; ++ } ++ } ++ } ++ else ++ { ++ /* Write data to DR */ ++ hi2c->Instance->DR = (*hi2c->pBuffPtr++); ++ hi2c->XferCount--; ++ } ++ } ++ } ++ return HAL_OK; ++} ++ ++/** ++ * @brief Handle BTF flag for Master transmitter ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_MasterTransmit_BTF(I2C_HandleTypeDef *hi2c) ++{ ++ /* Declaration of temporary variables to prevent undefined behavior of volatile usage */ ++ uint32_t CurrentXferOptions = hi2c->XferOptions; ++ ++ if(hi2c->State == HAL_I2C_STATE_BUSY_TX) ++ { ++ if(hi2c->XferCount != 0U) ++ { ++ /* Write data to DR */ ++ hi2c->Instance->DR = (*hi2c->pBuffPtr++); ++ hi2c->XferCount--; ++ } ++ else ++ { ++ /* Call TxCpltCallback() directly if no stop mode is set */ ++ if((CurrentXferOptions != I2C_FIRST_AND_LAST_FRAME) && (CurrentXferOptions != I2C_LAST_FRAME) && (CurrentXferOptions != I2C_NO_OPTION_FRAME)) ++ { ++ __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ ++ hi2c->PreviousState = I2C_STATE_MASTER_BUSY_TX; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ hi2c->State = HAL_I2C_STATE_READY; ++ ++ HAL_I2C_MasterTxCpltCallback(hi2c); ++ } ++ else /* Generate Stop condition then Call TxCpltCallback() */ ++ { ++ /* Disable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State = HAL_I2C_STATE_READY; ++ ++ if(hi2c->Mode == HAL_I2C_MODE_MEM) ++ { ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ HAL_I2C_MemTxCpltCallback(hi2c); ++ } ++ else ++ { ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ HAL_I2C_MasterTxCpltCallback(hi2c); ++ } ++ } ++ } ++ } ++ return HAL_OK; ++} ++ ++/** ++ * @brief Handle RXNE flag for Master ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_MasterReceive_RXNE(I2C_HandleTypeDef *hi2c) ++{ ++ if(hi2c->State == HAL_I2C_STATE_BUSY_RX) ++ { ++ uint32_t tmp = 0U; ++ ++ tmp = hi2c->XferCount; ++ if(tmp > 3U) ++ { ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferCount--; ++ ++ if(hi2c->XferCount == 3) ++ { ++ /* Disable BUF interrupt, this help to treat correctly the last 4 bytes ++ on BTF subroutine */ ++ /* Disable BUF interrupt */ ++ __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_BUF); ++ } ++ } ++ else if((tmp == 1U) || (tmp == 0U)) ++ { ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ /* Disable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferCount--; ++ ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->PreviousState = I2C_STATE_NONE; ++ ++ if(hi2c->Mode == HAL_I2C_MODE_MEM) ++ { ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ HAL_I2C_MemRxCpltCallback(hi2c); ++ } ++ else ++ { ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ HAL_I2C_MasterRxCpltCallback(hi2c); ++ } ++ } ++ } ++ return HAL_OK; ++} ++ ++/** ++ * @brief Handle BTF flag for Master receiver ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_MasterReceive_BTF(I2C_HandleTypeDef *hi2c) ++{ ++ /* Declaration of temporary variables to prevent undefined behavior of volatile usage */ ++ uint32_t CurrentXferOptions = hi2c->XferOptions; ++ ++ if(hi2c->XferCount == 4U) ++ { ++ /* Disable BUF interrupt, this help to treat correctly the last 2 bytes ++ on BTF subroutine if there is a reception delay between N-1 and N byte */ ++ __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_BUF); ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferCount--; ++ } ++ else if(hi2c->XferCount == 3U) ++ { ++ /* Disable BUF interrupt, this help to treat correctly the last 2 bytes ++ on BTF subroutine if there is a reception delay between N-1 and N byte */ ++ __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_BUF); ++ ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferCount--; ++ } ++ else if(hi2c->XferCount == 2U) ++ { ++ /* Prepare next transfer or stop current transfer */ ++ if((CurrentXferOptions == I2C_NEXT_FRAME) || (CurrentXferOptions == I2C_FIRST_FRAME)) ++ { ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ /* Generate ReStart */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ } ++ else ++ { ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ } ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferCount--; ++ ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferCount--; ++ ++ /* Disable EVT and ERR interrupt */ ++ __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_ERR); ++ ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->PreviousState = I2C_STATE_NONE; ++ ++ if(hi2c->Mode == HAL_I2C_MODE_MEM) ++ { ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ HAL_I2C_MemRxCpltCallback(hi2c); ++ } ++ else ++ { ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ HAL_I2C_MasterRxCpltCallback(hi2c); ++ } ++ } ++ else ++ { ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferCount--; ++ } ++ return HAL_OK; ++} ++ ++/** ++ * @brief Handle SB flag for Master ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_Master_SB(I2C_HandleTypeDef *hi2c) ++{ ++ if(hi2c->Mode == HAL_I2C_MODE_MEM) ++ { ++ if(hi2c->EventCount == 0U) ++ { ++ /* Send slave address */ ++ hi2c->Instance->DR = I2C_7BIT_ADD_WRITE(hi2c->Devaddress); ++ } ++ else ++ { ++ hi2c->Instance->DR = I2C_7BIT_ADD_READ(hi2c->Devaddress); ++ } ++ } ++ else ++ { ++ if(hi2c->Init.AddressingMode == I2C_ADDRESSINGMODE_7BIT) ++ { ++ /* Send slave 7 Bits address */ ++ if(hi2c->State == HAL_I2C_STATE_BUSY_TX) ++ { ++ hi2c->Instance->DR = I2C_7BIT_ADD_WRITE(hi2c->Devaddress); ++ } ++ else ++ { ++ hi2c->Instance->DR = I2C_7BIT_ADD_READ(hi2c->Devaddress); ++ } ++ } ++ else ++ { ++ if(hi2c->EventCount == 0U) ++ { ++ /* Send header of slave address */ ++ hi2c->Instance->DR = I2C_10BIT_HEADER_WRITE(hi2c->Devaddress); ++ } ++ else if(hi2c->EventCount == 1U) ++ { ++ /* Send header of slave address */ ++ hi2c->Instance->DR = I2C_10BIT_HEADER_READ(hi2c->Devaddress); ++ } ++ } ++ } ++ ++ return HAL_OK; ++} ++ ++/** ++ * @brief Handle ADD10 flag for Master ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_Master_ADD10(I2C_HandleTypeDef *hi2c) ++{ ++ /* Send slave address */ ++ hi2c->Instance->DR = I2C_10BIT_ADDRESS(hi2c->Devaddress); ++ ++ return HAL_OK; ++} ++ ++/** ++ * @brief Handle ADDR flag for Master ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_Master_ADDR(I2C_HandleTypeDef *hi2c) ++{ ++ /* Declaration of temporary variable to prevent undefined behavior of volatile usage */ ++ uint32_t CurrentMode = hi2c->Mode; ++ uint32_t CurrentXferOptions = hi2c->XferOptions; ++ uint32_t Prev_State = hi2c->PreviousState; ++ ++ if(hi2c->State == HAL_I2C_STATE_BUSY_RX) ++ { ++ if((hi2c->EventCount == 0U) && (CurrentMode == HAL_I2C_MODE_MEM)) ++ { ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ } ++ else if((hi2c->EventCount == 0U) && (hi2c->Init.AddressingMode == I2C_ADDRESSINGMODE_10BIT)) ++ { ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ /* Generate Restart */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ ++ hi2c->EventCount++; ++ } ++ else ++ { ++ if(hi2c->XferCount == 0U) ++ { ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ } ++ else if(hi2c->XferCount == 1U) ++ { ++ if(CurrentXferOptions == I2C_NO_OPTION_FRAME) ++ { ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ if((hi2c->Instance->CR2 & I2C_CR2_DMAEN) == I2C_CR2_DMAEN) ++ { ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ } ++ else ++ { ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ } ++ } ++ /* Prepare next transfer or stop current transfer */ ++ else if((CurrentXferOptions != I2C_FIRST_AND_LAST_FRAME) && (CurrentXferOptions != I2C_LAST_FRAME) \ ++ && (Prev_State != I2C_STATE_MASTER_BUSY_RX)) ++ { ++ if(hi2c->XferOptions != I2C_NEXT_FRAME) ++ { ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ } ++ else ++ { ++ /* Enable Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ } ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ } ++ else ++ { ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ } ++ } ++ else if(hi2c->XferCount == 2U) ++ { ++ if(hi2c->XferOptions != I2C_NEXT_FRAME) ++ { ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ /* Enable Pos */ ++ hi2c->Instance->CR1 |= I2C_CR1_POS; ++ } ++ else ++ { ++ /* Enable Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ } ++ ++ if((hi2c->Instance->CR2 & I2C_CR2_DMAEN) == I2C_CR2_DMAEN) ++ { ++ /* Enable Last DMA bit */ ++ hi2c->Instance->CR2 |= I2C_CR2_LAST; ++ } ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ } ++ else ++ { ++ /* Enable Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ if((hi2c->Instance->CR2 & I2C_CR2_DMAEN) == I2C_CR2_DMAEN) ++ { ++ /* Enable Last DMA bit */ ++ hi2c->Instance->CR2 |= I2C_CR2_LAST; ++ } ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ } ++ ++ /* Reset Event counter */ ++ hi2c->EventCount = 0U; ++ } ++ } ++ else ++ { ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ } ++ ++ return HAL_OK; ++} ++ ++/** ++ * @brief Handle TXE flag for Slave ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_SlaveTransmit_TXE(I2C_HandleTypeDef *hi2c) ++{ ++ /* Declaration of temporary variables to prevent undefined behavior of volatile usage */ ++ uint32_t CurrentState = hi2c->State; ++ ++ if(hi2c->XferCount != 0U) ++ { ++ /* Write data to DR */ ++ hi2c->Instance->DR = (*hi2c->pBuffPtr++); ++ hi2c->XferCount--; ++ ++ if((hi2c->XferCount == 0U) && (CurrentState == HAL_I2C_STATE_BUSY_TX_LISTEN)) ++ { ++ /* Last Byte is received, disable Interrupt */ ++ __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_BUF); ++ ++ /* Set state at HAL_I2C_STATE_LISTEN */ ++ hi2c->PreviousState = I2C_STATE_SLAVE_BUSY_TX; ++ hi2c->State = HAL_I2C_STATE_LISTEN; ++ ++ /* Call the Tx complete callback to inform upper layer of the end of receive process */ ++ HAL_I2C_SlaveTxCpltCallback(hi2c); ++ } ++ } ++ return HAL_OK; ++} ++ ++/** ++ * @brief Handle BTF flag for Slave transmitter ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_SlaveTransmit_BTF(I2C_HandleTypeDef *hi2c) ++{ ++ if(hi2c->XferCount != 0U) ++ { ++ /* Write data to DR */ ++ hi2c->Instance->DR = (*hi2c->pBuffPtr++); ++ hi2c->XferCount--; ++ } ++ return HAL_OK; ++} ++ ++/** ++ * @brief Handle RXNE flag for Slave ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_SlaveReceive_RXNE(I2C_HandleTypeDef *hi2c) ++{ ++ /* Declaration of temporary variables to prevent undefined behavior of volatile usage */ ++ uint32_t CurrentState = hi2c->State; ++ ++ if(hi2c->XferCount != 0U) ++ { ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferCount--; ++ ++ if((hi2c->XferCount == 0U) && (CurrentState == HAL_I2C_STATE_BUSY_RX_LISTEN)) ++ { ++ /* Last Byte is received, disable Interrupt */ ++ __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_BUF); ++ ++ /* Set state at HAL_I2C_STATE_LISTEN */ ++ hi2c->PreviousState = I2C_STATE_SLAVE_BUSY_RX; ++ hi2c->State = HAL_I2C_STATE_LISTEN; ++ ++ /* Call the Rx complete callback to inform upper layer of the end of receive process */ ++ HAL_I2C_SlaveRxCpltCallback(hi2c); ++ } ++ } ++ return HAL_OK; ++} ++ ++/** ++ * @brief Handle BTF flag for Slave receiver ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_SlaveReceive_BTF(I2C_HandleTypeDef *hi2c) ++{ ++ if(hi2c->XferCount != 0U) ++ { ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferCount--; ++ } ++ return HAL_OK; ++} ++ ++/** ++ * @brief Handle ADD flag for Slave ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_Slave_ADDR(I2C_HandleTypeDef *hi2c) ++{ ++ uint8_t TransferDirection = I2C_DIRECTION_RECEIVE; ++ uint16_t SlaveAddrCode = 0U; ++ ++ /* Transfer Direction requested by Master */ ++ if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_TRA) == RESET) ++ { ++ TransferDirection = I2C_DIRECTION_TRANSMIT; ++ } ++ ++ if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_DUALF) == RESET) ++ { ++ SlaveAddrCode = hi2c->Init.OwnAddress1; ++ } ++ else ++ { ++ SlaveAddrCode = hi2c->Init.OwnAddress2; ++ } ++ ++ /* Call Slave Addr callback */ ++ HAL_I2C_AddrCallback(hi2c, TransferDirection, SlaveAddrCode); ++ ++ return HAL_OK; ++} ++ ++/** ++ * @brief Handle STOPF flag for Slave ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_Slave_STOPF(I2C_HandleTypeDef *hi2c) ++{ ++ /* Declaration of temporary variable to prevent undefined behavior of volatile usage */ ++ uint32_t CurrentState = hi2c->State; ++ ++ /* Disable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ ++ /* Clear STOPF flag */ ++ __HAL_I2C_CLEAR_STOPFLAG(hi2c); ++ ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ /* If a DMA is ongoing, Update handle size context */ ++ if((hi2c->Instance->CR2 & I2C_CR2_DMAEN) == I2C_CR2_DMAEN) ++ { ++ if((hi2c->State == HAL_I2C_STATE_BUSY_RX) || (hi2c->State == HAL_I2C_STATE_BUSY_RX_LISTEN)) ++ { ++ hi2c->XferCount = __HAL_DMA_GET_COUNTER(hi2c->hdmarx); ++ } ++ else ++ { ++ hi2c->XferCount = __HAL_DMA_GET_COUNTER(hi2c->hdmatx); ++ } ++ } ++ ++ /* All data are not transferred, so set error code accordingly */ ++ if(hi2c->XferCount != 0U) ++ { ++ /* Store Last receive data if any */ ++ if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == SET) ++ { ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferCount--; ++ } ++ ++ /* Store Last receive data if any */ ++ if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_RXNE) == SET) ++ { ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ hi2c->XferCount--; ++ } ++ ++ /* Set ErrorCode corresponding to a Non-Acknowledge */ ++ //hi2c->ErrorCode |= HAL_I2C_ERROR_AF; ++ } ++ ++ if(hi2c->ErrorCode != HAL_I2C_ERROR_NONE) ++ { ++ /* Call the corresponding callback to inform upper layer of End of Transfer */ ++ I2C_ITError(hi2c); ++ } ++ else ++ { ++ if((CurrentState == HAL_I2C_STATE_LISTEN ) || (CurrentState == HAL_I2C_STATE_BUSY_RX_LISTEN) || \ ++ (CurrentState == HAL_I2C_STATE_BUSY_TX_LISTEN)) ++ { ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ /* Call the Listen Complete callback, to inform upper layer of the end of Listen usecase */ ++ HAL_I2C_ListenCpltCallback(hi2c); ++ } ++ else ++ { ++ if((hi2c->PreviousState == I2C_STATE_SLAVE_BUSY_RX) || (CurrentState == HAL_I2C_STATE_BUSY_RX)) ++ { ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ HAL_I2C_SlaveRxCpltCallback(hi2c); ++ } ++ } ++ } ++ return HAL_OK; ++} ++ ++/** ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_Slave_AF(I2C_HandleTypeDef *hi2c) ++{ ++ /* Declaration of temporary variables to prevent undefined behavior of volatile usage */ ++ uint32_t CurrentState = hi2c->State; ++ uint32_t CurrentXferOptions = hi2c->XferOptions; ++ ++ if(((CurrentXferOptions == I2C_FIRST_AND_LAST_FRAME) || (CurrentXferOptions == I2C_LAST_FRAME)) && \ ++ (CurrentState == HAL_I2C_STATE_LISTEN)) ++ { ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ ++ /* Disable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ ++ /* Clear AF flag */ ++ __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF); ++ ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ /* Call the Listen Complete callback, to inform upper layer of the end of Listen usecase */ ++ HAL_I2C_ListenCpltCallback(hi2c); ++ } ++ else if(CurrentState == HAL_I2C_STATE_BUSY_TX) ++ { ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->PreviousState = I2C_STATE_SLAVE_BUSY_TX; ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ /* Disable EVT, BUF and ERR interrupt */ ++ __HAL_I2C_DISABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_BUF | I2C_IT_ERR); ++ ++ /* Clear AF flag */ ++ __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF); ++ ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ HAL_I2C_SlaveTxCpltCallback(hi2c); ++ } ++ else ++ { ++ /* Clear AF flag only */ ++ /* State Listen, but XferOptions == FIRST or NEXT */ ++ __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF); ++ } ++ ++ return HAL_OK; ++} ++ ++/** ++ * @brief I2C interrupts error process ++ * @param hi2c I2C handle. ++ * @retval None ++ */ ++static void I2C_ITError(I2C_HandleTypeDef *hi2c) ++{ ++ /* Declaration of temporary variable to prevent undefined behavior of volatile usage */ ++ uint32_t CurrentState = hi2c->State; ++ ++ if((CurrentState == HAL_I2C_STATE_BUSY_TX_LISTEN) || (CurrentState == HAL_I2C_STATE_BUSY_RX_LISTEN)) ++ { ++ /* keep HAL_I2C_STATE_LISTEN */ ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State = HAL_I2C_STATE_LISTEN; ++ } ++ else ++ { ++ /* If state is an abort treatment on going, don't change state */ ++ /* This change will be do later */ ++ if((hi2c->State != HAL_I2C_STATE_ABORT) && ((hi2c->Instance->CR2 & I2C_CR2_DMAEN) != I2C_CR2_DMAEN)) ++ { ++ hi2c->State = HAL_I2C_STATE_READY; ++ } ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ } ++ ++ /* Disable Pos bit in I2C CR1 when error occurred in Master/Mem Receive IT Process */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_POS; ++ ++ /* Abort DMA transfer */ ++ if((hi2c->Instance->CR2 & I2C_CR2_DMAEN) == I2C_CR2_DMAEN) ++ { ++ hi2c->Instance->CR2 &= ~I2C_CR2_DMAEN; ++ ++ if(hi2c->hdmatx->State != HAL_DMA_STATE_READY) ++ { ++ /* Set the DMA Abort callback : ++ will lead to call HAL_I2C_ErrorCallback() at end of DMA abort procedure */ ++ hi2c->hdmatx->XferAbortCallback = I2C_DMAAbort; ++ ++ if(HAL_DMA_Abort_IT(hi2c->hdmatx) != HAL_OK) ++ { ++ /* Disable I2C peripheral to prevent dummy data in buffer */ ++ __HAL_I2C_DISABLE(hi2c); ++ ++ hi2c->State = HAL_I2C_STATE_READY; ++ ++ /* Call Directly XferAbortCallback function in case of error */ ++ hi2c->hdmatx->XferAbortCallback(hi2c->hdmatx); ++ } ++ } ++ else ++ { ++ /* Set the DMA Abort callback : ++ will lead to call HAL_I2C_ErrorCallback() at end of DMA abort procedure */ ++ hi2c->hdmarx->XferAbortCallback = I2C_DMAAbort; ++ ++ if(HAL_DMA_Abort_IT(hi2c->hdmarx) != HAL_OK) ++ { ++ /* Store Last receive data if any */ ++ if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_RXNE) == SET) ++ { ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ } ++ ++ /* Disable I2C peripheral to prevent dummy data in buffer */ ++ __HAL_I2C_DISABLE(hi2c); ++ ++ hi2c->State = HAL_I2C_STATE_READY; ++ ++ /* Call Directly hi2c->hdmarx->XferAbortCallback function in case of error */ ++ hi2c->hdmarx->XferAbortCallback(hi2c->hdmarx); ++ } ++ } ++ } ++ else if(hi2c->State == HAL_I2C_STATE_ABORT) ++ { ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Store Last receive data if any */ ++ if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_RXNE) == SET) ++ { ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ } ++ ++ /* Disable I2C peripheral to prevent dummy data in buffer */ ++ __HAL_I2C_DISABLE(hi2c); ++ ++ /* Call the corresponding callback to inform upper layer of End of Transfer */ ++ HAL_I2C_AbortCpltCallback(hi2c); ++ } ++ else ++ { ++ /* Store Last receive data if any */ ++ if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_RXNE) == SET) ++ { ++ /* Read data from DR */ ++ (*hi2c->pBuffPtr++) = hi2c->Instance->DR; ++ } ++ ++ /* Call user error callback */ ++ HAL_I2C_ErrorCallback(hi2c); ++ } ++ /* STOP Flag is not set after a NACK reception */ ++ /* So may inform upper layer that listen phase is stopped */ ++ /* during NACK error treatment */ ++ if((hi2c->State == HAL_I2C_STATE_LISTEN) && ((hi2c->ErrorCode & HAL_I2C_ERROR_AF) == HAL_I2C_ERROR_AF)) ++ { ++ hi2c->XferOptions = I2C_NO_OPTION_FRAME; ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ /* Call the Listen Complete callback, to inform upper layer of the end of Listen usecase */ ++ HAL_I2C_ListenCpltCallback(hi2c); ++ } ++} ++ ++/** ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @param DevAddress Target device address The device 7 bits address value ++ * in datasheet must be shifted to the left before calling the interface ++ * @param Timeout Timeout duration ++ * @param Tickstart Tick start value ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_MasterRequestWrite(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Timeout, uint32_t Tickstart) ++{ ++ /* Declaration of temporary variable to prevent undefined behavior of volatile usage */ ++ uint32_t CurrentXferOptions = hi2c->XferOptions; ++ ++ /* Generate Start condition if first transfer */ ++ if((CurrentXferOptions == I2C_FIRST_AND_LAST_FRAME) || (CurrentXferOptions == I2C_FIRST_FRAME) || (CurrentXferOptions == I2C_NO_OPTION_FRAME)) ++ { ++ /* Generate Start */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ } ++ else if(hi2c->PreviousState == I2C_STATE_MASTER_BUSY_RX) ++ { ++ /* Generate ReStart */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ } ++ ++ /* Wait until SB flag is set */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_SB, RESET, Timeout, Tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ ++ if(hi2c->Init.AddressingMode == I2C_ADDRESSINGMODE_7BIT) ++ { ++ /* Send slave address */ ++ hi2c->Instance->DR = I2C_7BIT_ADD_WRITE(DevAddress); ++ } ++ else ++ { ++ /* Send header of slave address */ ++ hi2c->Instance->DR = I2C_10BIT_HEADER_WRITE(DevAddress); ++ ++ /* Wait until ADD10 flag is set */ ++ if(I2C_WaitOnMasterAddressFlagUntilTimeout(hi2c, I2C_FLAG_ADD10, Timeout, Tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ /* Send slave address */ ++ hi2c->Instance->DR = I2C_10BIT_ADDRESS(DevAddress); ++ } ++ ++ /* Wait until ADDR flag is set */ ++ if(I2C_WaitOnMasterAddressFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, Timeout, Tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ return HAL_OK; ++} ++ ++/** ++ * @brief Master sends target device address for read request. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @param DevAddress Target device address The device 7 bits address value ++ * in datasheet must be shifted to the left before calling the interface ++ * @param Timeout Timeout duration ++ * @param Tickstart Tick start value ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_MasterRequestRead(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Timeout, uint32_t Tickstart) ++{ ++ /* Declaration of temporary variable to prevent undefined behavior of volatile usage */ ++ uint32_t CurrentXferOptions = hi2c->XferOptions; ++ ++ /* Enable Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ /* Generate Start condition if first transfer */ ++ if((CurrentXferOptions == I2C_FIRST_AND_LAST_FRAME) || (CurrentXferOptions == I2C_FIRST_FRAME) || (CurrentXferOptions == I2C_NO_OPTION_FRAME)) ++ { ++ /* Generate Start */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ } ++ else if(hi2c->PreviousState == I2C_STATE_MASTER_BUSY_TX) ++ { ++ /* Generate ReStart */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ } ++ ++ /* Wait until SB flag is set */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_SB, RESET, Timeout, Tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ ++ if(hi2c->Init.AddressingMode == I2C_ADDRESSINGMODE_7BIT) ++ { ++ /* Send slave address */ ++ hi2c->Instance->DR = I2C_7BIT_ADD_READ(DevAddress); ++ } ++ else ++ { ++ /* Send header of slave address */ ++ hi2c->Instance->DR = I2C_10BIT_HEADER_WRITE(DevAddress); ++ ++ /* Wait until ADD10 flag is set */ ++ if(I2C_WaitOnMasterAddressFlagUntilTimeout(hi2c, I2C_FLAG_ADD10, Timeout, Tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ /* Send slave address */ ++ hi2c->Instance->DR = I2C_10BIT_ADDRESS(DevAddress); ++ ++ /* Wait until ADDR flag is set */ ++ if(I2C_WaitOnMasterAddressFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, Timeout, Tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ /* Generate Restart */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ ++ /* Wait until SB flag is set */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_SB, RESET, Timeout, Tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ ++ /* Send header of slave address */ ++ hi2c->Instance->DR = I2C_10BIT_HEADER_READ(DevAddress); ++ } ++ ++ /* Wait until ADDR flag is set */ ++ if(I2C_WaitOnMasterAddressFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, Timeout, Tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ return HAL_OK; ++} ++ ++/** ++ * @brief Master sends target device address followed by internal memory address for write request. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @param DevAddress Target device address ++ * @param MemAddress Internal memory address ++ * @param MemAddSize Size of internal memory address ++ * @param Timeout Timeout duration ++ * @param Tickstart Tick start value ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_RequestMemoryWrite(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint32_t Timeout, uint32_t Tickstart) ++{ ++ /* Generate Start */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ ++ /* Wait until SB flag is set */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_SB, RESET, Timeout, Tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ ++ /* Send slave address */ ++ hi2c->Instance->DR = I2C_7BIT_ADD_WRITE(DevAddress); ++ ++ /* Wait until ADDR flag is set */ ++ if(I2C_WaitOnMasterAddressFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, Timeout, Tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ /* Wait until TXE flag is set */ ++ if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, Tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ /* If Memory address size is 8Bit */ ++ if(MemAddSize == I2C_MEMADD_SIZE_8BIT) ++ { ++ /* Send Memory Address */ ++ hi2c->Instance->DR = I2C_MEM_ADD_LSB(MemAddress); ++ } ++ /* If Memory address size is 16Bit */ ++ else ++ { ++ /* Send MSB of Memory Address */ ++ hi2c->Instance->DR = I2C_MEM_ADD_MSB(MemAddress); ++ ++ /* Wait until TXE flag is set */ ++ if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, Tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ /* Send LSB of Memory Address */ ++ hi2c->Instance->DR = I2C_MEM_ADD_LSB(MemAddress); ++ } ++ ++ return HAL_OK; ++} ++ ++/** ++ * @brief Master sends target device address followed by internal memory address for read request. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @param DevAddress Target device address ++ * @param MemAddress Internal memory address ++ * @param MemAddSize Size of internal memory address ++ * @param Timeout Timeout duration ++ * @param Tickstart Tick start value ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_RequestMemoryRead(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint32_t Timeout, uint32_t Tickstart) ++{ ++ /* Enable Acknowledge */ ++ hi2c->Instance->CR1 |= I2C_CR1_ACK; ++ ++ /* Generate Start */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ ++ /* Wait until SB flag is set */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_SB, RESET, Timeout, Tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ ++ /* Send slave address */ ++ hi2c->Instance->DR = I2C_7BIT_ADD_WRITE(DevAddress); ++ ++ /* Wait until ADDR flag is set */ ++ if(I2C_WaitOnMasterAddressFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, Timeout, Tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ /* Clear ADDR flag */ ++ __HAL_I2C_CLEAR_ADDRFLAG(hi2c); ++ ++ /* Wait until TXE flag is set */ ++ if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, Tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ /* If Memory address size is 8Bit */ ++ if(MemAddSize == I2C_MEMADD_SIZE_8BIT) ++ { ++ /* Send Memory Address */ ++ hi2c->Instance->DR = I2C_MEM_ADD_LSB(MemAddress); ++ } ++ /* If Memory address size is 16Bit */ ++ else ++ { ++ /* Send MSB of Memory Address */ ++ hi2c->Instance->DR = I2C_MEM_ADD_MSB(MemAddress); ++ ++ /* Wait until TXE flag is set */ ++ if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, Tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ /* Send LSB of Memory Address */ ++ hi2c->Instance->DR = I2C_MEM_ADD_LSB(MemAddress); ++ } ++ ++ /* Wait until TXE flag is set */ ++ if(I2C_WaitOnTXEFlagUntilTimeout(hi2c, Timeout, Tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ /* Generate Restart */ ++ hi2c->Instance->CR1 |= I2C_CR1_START; ++ ++ /* Wait until SB flag is set */ ++ if(I2C_WaitOnFlagUntilTimeout(hi2c, I2C_FLAG_SB, RESET, Timeout, Tickstart) != HAL_OK) ++ { ++ return HAL_TIMEOUT; ++ } ++ ++ /* Send slave address */ ++ hi2c->Instance->DR = I2C_7BIT_ADD_READ(DevAddress); ++ ++ /* Wait until ADDR flag is set */ ++ if(I2C_WaitOnMasterAddressFlagUntilTimeout(hi2c, I2C_FLAG_ADDR, Timeout, Tickstart) != HAL_OK) ++ { ++ if(hi2c->ErrorCode == HAL_I2C_ERROR_AF) ++ { ++ return HAL_ERROR; ++ } ++ else ++ { ++ return HAL_TIMEOUT; ++ } ++ } ++ ++ return HAL_OK; ++} ++ ++/** ++ * @brief DMA I2C process complete callback. ++ * @param hdma DMA handle ++ * @retval None ++ */ ++static void I2C_DMAXferCplt(DMA_HandleTypeDef *hdma) ++{ ++ I2C_HandleTypeDef* hi2c = (I2C_HandleTypeDef*)((DMA_HandleTypeDef*)hdma)->Parent; ++ ++ /* Declaration of temporary variable to prevent undefined behavior of volatile usage */ ++ uint32_t CurrentState = hi2c->State; ++ uint32_t CurrentMode = hi2c->Mode; ++ ++ if((CurrentState == HAL_I2C_STATE_BUSY_TX) || ((CurrentState == HAL_I2C_STATE_BUSY_RX) && (CurrentMode == HAL_I2C_MODE_SLAVE))) ++ { ++ /* Disable DMA Request */ ++ hi2c->Instance->CR2 &= ~I2C_CR2_DMAEN; ++ ++ hi2c->XferCount = 0U; ++ ++ /* Enable EVT and ERR interrupt */ ++ __HAL_I2C_ENABLE_IT(hi2c, I2C_IT_EVT | I2C_IT_ERR); ++ } ++ else ++ { ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ ++ /* Disable Last DMA */ ++ hi2c->Instance->CR2 &= ~I2C_CR2_LAST; ++ ++ /* Disable DMA Request */ ++ hi2c->Instance->CR2 &= ~I2C_CR2_DMAEN; ++ ++ hi2c->XferCount = 0U; ++ ++ /* Check if Errors has been detected during transfer */ ++ if(hi2c->ErrorCode != HAL_I2C_ERROR_NONE) ++ { ++ HAL_I2C_ErrorCallback(hi2c); ++ } ++ else ++ { ++ hi2c->State = HAL_I2C_STATE_READY; ++ ++ if(hi2c->Mode == HAL_I2C_MODE_MEM) ++ { ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ HAL_I2C_MemRxCpltCallback(hi2c); ++ } ++ else ++ { ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ HAL_I2C_MasterRxCpltCallback(hi2c); ++ } ++ } ++ } ++} ++ ++/** ++ * @brief DMA I2C communication error callback. ++ * @param hdma DMA handle ++ * @retval None ++ */ ++static void I2C_DMAError(DMA_HandleTypeDef *hdma) ++{ ++ I2C_HandleTypeDef* hi2c = (I2C_HandleTypeDef*)((DMA_HandleTypeDef*)hdma)->Parent; ++ ++ /* Ignore DMA FIFO error */ ++ if(HAL_DMA_GetError(hdma) != HAL_DMA_ERROR_FE) ++ { ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ hi2c->XferCount = 0U; ++ ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ hi2c->ErrorCode |= HAL_I2C_ERROR_DMA; ++ ++ HAL_I2C_ErrorCallback(hi2c); ++ } ++} ++ ++/** ++ * @brief DMA I2C communication abort callback ++ * (To be called at end of DMA Abort procedure). ++ * @param hdma DMA handle. ++ * @retval None ++ */ ++static void I2C_DMAAbort(DMA_HandleTypeDef *hdma) ++{ ++ I2C_HandleTypeDef* hi2c = ( I2C_HandleTypeDef* )((DMA_HandleTypeDef* )hdma)->Parent; ++ ++ /* Disable Acknowledge */ ++ hi2c->Instance->CR1 &= ~I2C_CR1_ACK; ++ ++ hi2c->XferCount = 0U; ++ ++ /* Reset XferAbortCallback */ ++ hi2c->hdmatx->XferAbortCallback = NULL; ++ hi2c->hdmarx->XferAbortCallback = NULL; ++ ++ /* Check if come from abort from user */ ++ if(hi2c->State == HAL_I2C_STATE_ABORT) ++ { ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ ++ /* Disable I2C peripheral to prevent dummy data in buffer */ ++ __HAL_I2C_DISABLE(hi2c); ++ ++ /* Call the corresponding callback to inform upper layer of End of Transfer */ ++ HAL_I2C_AbortCpltCallback(hi2c); ++ } ++ else ++ { ++ hi2c->State = HAL_I2C_STATE_READY; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ /* Disable I2C peripheral to prevent dummy data in buffer */ ++ __HAL_I2C_DISABLE(hi2c); ++ ++ /* Call the corresponding callback to inform upper layer of End of Transfer */ ++ HAL_I2C_ErrorCallback(hi2c); ++ } ++} ++ ++/** ++ * @brief This function handles I2C Communication Timeout. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @param Flag specifies the I2C flag to check. ++ * @param Status The new Flag status (SET or RESET). ++ * @param Timeout Timeout duration ++ * @param Tickstart Tick start value ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_WaitOnFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Flag, FlagStatus Status, uint32_t Timeout, uint32_t Tickstart) ++{ ++ /* Wait until flag is set */ ++ while((__HAL_I2C_GET_FLAG(hi2c, Flag) ? SET : RESET) == Status) ++ { ++ /* Check for the Timeout */ ++ if(Timeout != HAL_MAX_DELAY) ++ { ++ if((Timeout == 0U)||((HAL_GetTick() - Tickstart ) > Timeout)) ++ { ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ hi2c->Mode = HAL_I2C_MODE_NONE; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ } ++ ++ return HAL_OK; ++} ++ ++/** ++ * @brief This function handles I2C Communication Timeout for Master addressing phase. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for I2C module ++ * @param Flag specifies the I2C flag to check. ++ * @param Timeout Timeout duration ++ * @param Tickstart Tick start value ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_WaitOnMasterAddressFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Flag, uint32_t Timeout, uint32_t Tickstart) ++{ ++ while(__HAL_I2C_GET_FLAG(hi2c, Flag) == RESET) ++ { ++ if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_AF) == SET) ++ { ++ /* Generate Stop */ ++ hi2c->Instance->CR1 |= I2C_CR1_STOP; ++ ++ /* Clear AF Flag */ ++ __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF); ++ ++ hi2c->ErrorCode = HAL_I2C_ERROR_AF; ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_ERROR; ++ } ++ ++ /* Check for the Timeout */ ++ if(Timeout != HAL_MAX_DELAY) ++ { ++ if((Timeout == 0U)||((HAL_GetTick() - Tickstart ) > Timeout)) ++ { ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ } ++ return HAL_OK; ++} ++ ++/** ++ * @brief This function handles I2C Communication Timeout for specific usage of TXE flag. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param Timeout Timeout duration ++ * @param Tickstart Tick start value ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_WaitOnTXEFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout, uint32_t Tickstart) ++{ ++ while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_TXE) == RESET) ++ { ++ /* Check if a NACK is detected */ ++ if(I2C_IsAcknowledgeFailed(hi2c) != HAL_OK) ++ { ++ return HAL_ERROR; ++ } ++ ++ /* Check for the Timeout */ ++ if(Timeout != HAL_MAX_DELAY) ++ { ++ if((Timeout == 0U) || ((HAL_GetTick()-Tickstart) > Timeout)) ++ { ++ hi2c->ErrorCode |= HAL_I2C_ERROR_TIMEOUT; ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ } ++ return HAL_OK; ++} ++ ++/** ++ * @brief This function handles I2C Communication Timeout for specific usage of BTF flag. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param Timeout Timeout duration ++ * @param Tickstart Tick start value ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_WaitOnBTFFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout, uint32_t Tickstart) ++{ ++ while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_BTF) == RESET) ++ { ++ /* Check if a NACK is detected */ ++ if(I2C_IsAcknowledgeFailed(hi2c) != HAL_OK) ++ { ++ return HAL_ERROR; ++ } ++ ++ /* Check for the Timeout */ ++ if(Timeout != HAL_MAX_DELAY) ++ { ++ if((Timeout == 0U) || ((HAL_GetTick()-Tickstart) > Timeout)) ++ { ++ hi2c->ErrorCode |= HAL_I2C_ERROR_TIMEOUT; ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ } ++ return HAL_OK; ++} ++ ++/** ++ * @brief This function handles I2C Communication Timeout for specific usage of STOP flag. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param Timeout Timeout duration ++ * @param Tickstart Tick start value ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_WaitOnSTOPFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout, uint32_t Tickstart) ++{ ++ while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_STOPF) == RESET) ++ { ++ /* Check if a NACK is detected */ ++ if(I2C_IsAcknowledgeFailed(hi2c) != HAL_OK) ++ { ++ return HAL_ERROR; ++ } ++ ++ /* Check for the Timeout */ ++ if((Timeout == 0U) || ((HAL_GetTick()-Tickstart) > Timeout)) ++ { ++ hi2c->ErrorCode |= HAL_I2C_ERROR_TIMEOUT; ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ return HAL_OK; ++} ++ ++/** ++ * @brief This function handles I2C Communication Timeout for specific usage of RXNE flag. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @param Timeout Timeout duration ++ * @param Tickstart Tick start value ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_WaitOnRXNEFlagUntilTimeout(I2C_HandleTypeDef *hi2c, uint32_t Timeout, uint32_t Tickstart) ++{ ++ ++ while(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_RXNE) == RESET) ++ { ++ /* Check if a STOPF is detected */ ++ if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_STOPF) == SET) ++ { ++ /* Clear STOP Flag */ ++ __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_STOPF); ++ ++ hi2c->ErrorCode = HAL_I2C_ERROR_NONE; ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_ERROR; ++ } ++ ++ /* Check for the Timeout */ ++ if((Timeout == 0U) || ((HAL_GetTick()-Tickstart) > Timeout)) ++ { ++ hi2c->ErrorCode |= HAL_I2C_ERROR_TIMEOUT; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_TIMEOUT; ++ } ++ } ++ return HAL_OK; ++} ++ ++/** ++ * @brief This function handles Acknowledge failed detection during an I2C Communication. ++ * @param hi2c Pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2C. ++ * @retval HAL status ++ */ ++static HAL_StatusTypeDef I2C_IsAcknowledgeFailed(I2C_HandleTypeDef *hi2c) ++{ ++ if(__HAL_I2C_GET_FLAG(hi2c, I2C_FLAG_AF) == SET) ++ { ++ /* Clear NACKF Flag */ ++ __HAL_I2C_CLEAR_FLAG(hi2c, I2C_FLAG_AF); ++ ++ hi2c->ErrorCode = HAL_I2C_ERROR_AF; ++ hi2c->PreviousState = I2C_STATE_NONE; ++ hi2c->State= HAL_I2C_STATE_READY; ++ ++ /* Process Unlocked */ ++ __HAL_UNLOCK(hi2c); ++ ++ return HAL_ERROR; ++ } ++ return HAL_OK; ++} ++/** ++ * @} ++ */ ++ ++#endif /* HAL_I2C_MODULE_ENABLED */ ++ ++/** ++ * @} ++ */ ++ ++/** ++ * @} ++ */ ++ ++/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ +diff --git a/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c_ex.c b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c_ex.c +new file mode 100644 +index 0000000..de8f160 +--- /dev/null ++++ b/Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2c_ex.c +@@ -0,0 +1,204 @@ ++/** ++ ****************************************************************************** ++ * @file stm32f4xx_hal_i2c_ex.c ++ * @author MCD Application Team ++ * @brief I2C Extension HAL module driver. ++ * This file provides firmware functions to manage the following ++ * functionalities of I2C extension peripheral: ++ * + Extension features functions ++ * ++ @verbatim ++ ============================================================================== ++ ##### I2C peripheral extension features ##### ++ ============================================================================== ++ ++ [..] Comparing to other previous devices, the I2C interface for STM32F427xx/437xx/ ++ 429xx/439xx devices contains the following additional features : ++ ++ (+) Possibility to disable or enable Analog Noise Filter ++ (+) Use of a configured Digital Noise Filter ++ ++ ##### How to use this driver ##### ++ ============================================================================== ++ [..] This driver provides functions to configure Noise Filter ++ (#) Configure I2C Analog noise filter using the function HAL_I2C_AnalogFilter_Config() ++ (#) Configure I2C Digital noise filter using the function HAL_I2C_DigitalFilter_Config() ++ ++ @endverbatim ++ ****************************************************************************** ++ * @attention ++ * ++ *

© COPYRIGHT(c) 2017 STMicroelectronics

++ * ++ * Redistribution and use in source and binary forms, with or without modification, ++ * are permitted provided that the following conditions are met: ++ * 1. Redistributions of source code must retain the above copyright notice, ++ * this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright notice, ++ * this list of conditions and the following disclaimer in the documentation ++ * and/or other materials provided with the distribution. ++ * 3. Neither the name of STMicroelectronics nor the names of its contributors ++ * may be used to endorse or promote products derived from this software ++ * without specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE ++ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE ++ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ++ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR ++ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER ++ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ++ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ * ++ ****************************************************************************** ++ */ ++ ++/* Includes ------------------------------------------------------------------*/ ++#include "stm32f4xx_hal.h" ++ ++/** @addtogroup STM32F4xx_HAL_Driver ++ * @{ ++ */ ++ ++/** @defgroup I2CEx I2CEx ++ * @brief I2C HAL module driver ++ * @{ ++ */ ++ ++#ifdef HAL_I2C_MODULE_ENABLED ++ ++#if defined(STM32F427xx) || defined(STM32F437xx) || defined(STM32F429xx) || defined(STM32F439xx) ||\ ++ defined(STM32F401xC) || defined(STM32F401xE) || defined(STM32F411xE) || defined(STM32F446xx) ||\ ++ defined(STM32F469xx) || defined(STM32F479xx) || defined(STM32F413xx) || defined(STM32F423xx) ++/* Private typedef -----------------------------------------------------------*/ ++/* Private define ------------------------------------------------------------*/ ++/* Private macro -------------------------------------------------------------*/ ++/* Private variables ---------------------------------------------------------*/ ++/* Private function prototypes -----------------------------------------------*/ ++/* Exported functions --------------------------------------------------------*/ ++/** @defgroup I2CEx_Exported_Functions I2C Exported Functions ++ * @{ ++ */ ++ ++ ++/** @defgroup I2CEx_Exported_Functions_Group1 Extension features functions ++ * @brief Extension features functions ++ * ++@verbatim ++ =============================================================================== ++ ##### Extension features functions ##### ++ =============================================================================== ++ [..] This section provides functions allowing to: ++ (+) Configure Noise Filters ++ ++@endverbatim ++ * @{ ++ */ ++ ++/** ++ * @brief Configures I2C Analog noise filter. ++ * @param hi2c pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2Cx peripheral. ++ * @param AnalogFilter new state of the Analog filter. ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2CEx_ConfigAnalogFilter(I2C_HandleTypeDef *hi2c, uint32_t AnalogFilter) ++{ ++ /* Check the parameters */ ++ assert_param(IS_I2C_ALL_INSTANCE(hi2c->Instance)); ++ assert_param(IS_I2C_ANALOG_FILTER(AnalogFilter)); ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ hi2c->State = HAL_I2C_STATE_BUSY; ++ ++ /* Disable the selected I2C peripheral */ ++ __HAL_I2C_DISABLE(hi2c); ++ ++ /* Reset I2Cx ANOFF bit */ ++ hi2c->Instance->FLTR &= ~(I2C_FLTR_ANOFF); ++ ++ /* Disable the analog filter */ ++ hi2c->Instance->FLTR |= AnalogFilter; ++ ++ __HAL_I2C_ENABLE(hi2c); ++ ++ hi2c->State = HAL_I2C_STATE_READY; ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @brief Configures I2C Digital noise filter. ++ * @param hi2c pointer to a I2C_HandleTypeDef structure that contains ++ * the configuration information for the specified I2Cx peripheral. ++ * @param DigitalFilter Coefficient of digital noise filter between 0x00 and 0x0F. ++ * @retval HAL status ++ */ ++HAL_StatusTypeDef HAL_I2CEx_ConfigDigitalFilter(I2C_HandleTypeDef *hi2c, uint32_t DigitalFilter) ++{ ++ uint16_t tmpreg = 0; ++ ++ /* Check the parameters */ ++ assert_param(IS_I2C_ALL_INSTANCE(hi2c->Instance)); ++ assert_param(IS_I2C_DIGITAL_FILTER(DigitalFilter)); ++ ++ if(hi2c->State == HAL_I2C_STATE_READY) ++ { ++ hi2c->State = HAL_I2C_STATE_BUSY; ++ ++ /* Disable the selected I2C peripheral */ ++ __HAL_I2C_DISABLE(hi2c); ++ ++ /* Get the old register value */ ++ tmpreg = hi2c->Instance->FLTR; ++ ++ /* Reset I2Cx DNF bit [3:0] */ ++ tmpreg &= ~(I2C_FLTR_DNF); ++ ++ /* Set I2Cx DNF coefficient */ ++ tmpreg |= DigitalFilter; ++ ++ /* Store the new register value */ ++ hi2c->Instance->FLTR = tmpreg; ++ ++ __HAL_I2C_ENABLE(hi2c); ++ ++ hi2c->State = HAL_I2C_STATE_READY; ++ ++ return HAL_OK; ++ } ++ else ++ { ++ return HAL_BUSY; ++ } ++} ++ ++/** ++ * @} ++ */ ++ ++/** ++ * @} ++ */ ++#endif /* STM32F427xx || STM32F429xx || STM32F437xx || STM32F439xx || STM32F401xC ||\ ++ STM32F401xE || STM32F446xx || STM32F469xx || STM32F479xx || STM32F413xx ||\ ++ STM32F423xx */ ++ ++#endif /* HAL_I2C_MODULE_ENABLED */ ++/** ++ * @} ++ */ ++ ++/** ++ * @} ++ */ ++ ++/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ +diff --git a/Firmware/Board/v3/Inc/i2c.h b/Firmware/Board/v3/Inc/i2c.h +new file mode 100644 +index 0000000..f449b88 +--- /dev/null ++++ b/Firmware/Board/v3/Inc/i2c.h +@@ -0,0 +1,91 @@ ++/** ++ ****************************************************************************** ++ * File Name : I2C.h ++ * Description : This file provides code for the configuration ++ * of the I2C instances. ++ ****************************************************************************** ++ * This notice applies to any and all portions of this file ++ * that are not between comment pairs USER CODE BEGIN and ++ * USER CODE END. Other portions of this file, whether ++ * inserted by the user or by software development tools ++ * are owned by their respective copyright owners. ++ * ++ * Copyright (c) 2018 STMicroelectronics International N.V. ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted, provided that the following conditions are met: ++ * ++ * 1. Redistribution of source code must retain the above copyright notice, ++ * this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright notice, ++ * this list of conditions and the following disclaimer in the documentation ++ * and/or other materials provided with the distribution. ++ * 3. Neither the name of STMicroelectronics nor the names of other ++ * contributors to this software may be used to endorse or promote products ++ * derived from this software without specific written permission. ++ * 4. This software, including modifications and/or derivative works of this ++ * software, must execute solely and exclusively on microcontroller or ++ * microprocessor devices manufactured by or for STMicroelectronics. ++ * 5. Redistribution and use of this software other than as permitted under ++ * this license is void and will automatically terminate your rights under ++ * this license. ++ * ++ * THIS SOFTWARE IS PROVIDED BY STMICROELECTRONICS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT ++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A ++ * PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY ++ * RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT ++ * SHALL STMICROELECTRONICS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, ++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, ++ * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF ++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, ++ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ * ++ ****************************************************************************** ++ */ ++/* Define to prevent recursive inclusion -------------------------------------*/ ++#ifndef __i2c_H ++#define __i2c_H ++#ifdef __cplusplus ++ extern "C" { ++#endif ++ ++/* Includes ------------------------------------------------------------------*/ ++#include "stm32f4xx_hal.h" ++#include "main.h" ++ ++/* USER CODE BEGIN Includes */ ++ ++/* USER CODE END Includes */ ++ ++extern I2C_HandleTypeDef hi2c1; ++ ++/* USER CODE BEGIN Private defines */ ++ ++/* USER CODE END Private defines */ ++ ++extern void _Error_Handler(char *, int); ++ ++void MX_I2C1_Init(uint8_t addr); ++ ++/* USER CODE BEGIN Prototypes */ ++ ++/* USER CODE END Prototypes */ ++ ++#ifdef __cplusplus ++} ++#endif ++#endif /*__ i2c_H */ ++ ++/** ++ * @} ++ */ ++ ++/** ++ * @} ++ */ ++ ++/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ +diff --git a/Firmware/Board/v3/Inc/stm32f4xx_hal_conf.h b/Firmware/Board/v3/Inc/stm32f4xx_hal_conf.h +index d0f48f5..b3ef59a 100644 +--- a/Firmware/Board/v3/Inc/stm32f4xx_hal_conf.h ++++ b/Firmware/Board/v3/Inc/stm32f4xx_hal_conf.h +@@ -65,7 +65,7 @@ + /* #define HAL_SRAM_MODULE_ENABLED */ + /* #define HAL_SDRAM_MODULE_ENABLED */ + /* #define HAL_HASH_MODULE_ENABLED */ +-/* #define HAL_I2C_MODULE_ENABLED */ ++#define HAL_I2C_MODULE_ENABLED + /* #define HAL_I2S_MODULE_ENABLED */ + /* #define HAL_IWDG_MODULE_ENABLED */ + /* #define HAL_LTDC_MODULE_ENABLED */ +diff --git a/Firmware/Board/v3/Inc/stm32f4xx_it.h b/Firmware/Board/v3/Inc/stm32f4xx_it.h +index a4d1813..050334c 100644 +--- a/Firmware/Board/v3/Inc/stm32f4xx_it.h ++++ b/Firmware/Board/v3/Inc/stm32f4xx_it.h +@@ -59,6 +59,7 @@ void DMA1_Stream2_IRQHandler(void); + void DMA1_Stream4_IRQHandler(void); + void DMA1_Stream6_IRQHandler(void); + void ADC_IRQHandler(void); ++void I2C1_ER_IRQHandler(void); + void TIM8_TRG_COM_TIM14_IRQHandler(void); + void UART4_IRQHandler(void); + void OTG_FS_IRQHandler(void); +diff --git a/Firmware/Board/v3/Src/i2c.c b/Firmware/Board/v3/Src/i2c.c +new file mode 100644 +index 0000000..bae77f1 +--- /dev/null ++++ b/Firmware/Board/v3/Src/i2c.c +@@ -0,0 +1,198 @@ ++/** ++ ****************************************************************************** ++ * File Name : I2C.c ++ * Description : This file provides code for the configuration ++ * of the I2C instances. ++ ****************************************************************************** ++ * This notice applies to any and all portions of this file ++ * that are not between comment pairs USER CODE BEGIN and ++ * USER CODE END. Other portions of this file, whether ++ * inserted by the user or by software development tools ++ * are owned by their respective copyright owners. ++ * ++ * Copyright (c) 2018 STMicroelectronics International N.V. ++ * All rights reserved. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted, provided that the following conditions are met: ++ * ++ * 1. Redistribution of source code must retain the above copyright notice, ++ * this list of conditions and the following disclaimer. ++ * 2. Redistributions in binary form must reproduce the above copyright notice, ++ * this list of conditions and the following disclaimer in the documentation ++ * and/or other materials provided with the distribution. ++ * 3. Neither the name of STMicroelectronics nor the names of other ++ * contributors to this software may be used to endorse or promote products ++ * derived from this software without specific written permission. ++ * 4. This software, including modifications and/or derivative works of this ++ * software, must execute solely and exclusively on microcontroller or ++ * microprocessor devices manufactured by or for STMicroelectronics. ++ * 5. Redistribution and use of this software other than as permitted under ++ * this license is void and will automatically terminate your rights under ++ * this license. ++ * ++ * THIS SOFTWARE IS PROVIDED BY STMICROELECTRONICS AND CONTRIBUTORS "AS IS" ++ * AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT ++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A ++ * PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY ++ * RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT ++ * SHALL STMICROELECTRONICS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, ++ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, ++ * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF ++ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING ++ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, ++ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ++ * ++ ****************************************************************************** ++ */ ++ ++/* Includes ------------------------------------------------------------------*/ ++#include "i2c.h" ++ ++#include "gpio.h" ++#include "dma.h" ++ ++/* USER CODE BEGIN 0 */ ++ ++/* USER CODE END 0 */ ++ ++I2C_HandleTypeDef hi2c1; ++DMA_HandleTypeDef hdma_i2c1_rx; ++DMA_HandleTypeDef hdma_i2c1_tx; ++ ++/* I2C1 init function */ ++void MX_I2C1_Init(uint8_t addr) ++{ ++ ++ hi2c1.Instance = I2C1; ++ hi2c1.Init.ClockSpeed = 100000; ++ hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; ++ hi2c1.Init.OwnAddress1 = addr << 1; ++ hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; ++ hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; ++ hi2c1.Init.OwnAddress2 = 0; ++ hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; ++ hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; ++ if (HAL_I2C_Init(&hi2c1) != HAL_OK) ++ { ++ _Error_Handler(__FILE__, __LINE__); ++ } ++ ++} ++ ++void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle) ++{ ++ ++ GPIO_InitTypeDef GPIO_InitStruct; ++ if(i2cHandle->Instance==I2C1) ++ { ++ /* USER CODE BEGIN I2C1_MspInit 0 */ ++ ++ /* USER CODE END I2C1_MspInit 0 */ ++ ++ /**I2C1 GPIO Configuration ++ PB8 ------> I2C1_SCL ++ PB9 ------> I2C1_SDA ++ */ ++ GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9; ++ GPIO_InitStruct.Mode = GPIO_MODE_AF_OD; ++ GPIO_InitStruct.Pull = GPIO_PULLUP; ++ GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; ++ GPIO_InitStruct.Alternate = GPIO_AF4_I2C1; ++ HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); ++ ++ /* I2C1 clock enable */ ++ __HAL_RCC_I2C1_CLK_ENABLE(); ++ ++ /* I2C1 DMA Init */ ++ /* I2C1_RX Init */ ++ hdma_i2c1_rx.Instance = DMA1_Stream0; ++ hdma_i2c1_rx.Init.Channel = DMA_CHANNEL_1; ++ hdma_i2c1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; ++ hdma_i2c1_rx.Init.PeriphInc = DMA_PINC_DISABLE; ++ hdma_i2c1_rx.Init.MemInc = DMA_MINC_ENABLE; ++ hdma_i2c1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; ++ hdma_i2c1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; ++ hdma_i2c1_rx.Init.Mode = DMA_CIRCULAR; ++ hdma_i2c1_rx.Init.Priority = DMA_PRIORITY_LOW; ++ hdma_i2c1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; ++ if (HAL_DMA_Init(&hdma_i2c1_rx) != HAL_OK) ++ { ++ _Error_Handler(__FILE__, __LINE__); ++ } ++ ++ __HAL_LINKDMA(i2cHandle,hdmarx,hdma_i2c1_rx); ++ ++ /* I2C1_TX Init */ ++ hdma_i2c1_tx.Instance = DMA1_Stream6; ++ hdma_i2c1_tx.Init.Channel = DMA_CHANNEL_1; ++ hdma_i2c1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; ++ hdma_i2c1_tx.Init.PeriphInc = DMA_PINC_DISABLE; ++ hdma_i2c1_tx.Init.MemInc = DMA_MINC_ENABLE; ++ hdma_i2c1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; ++ hdma_i2c1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; ++ hdma_i2c1_tx.Init.Mode = DMA_NORMAL; ++ hdma_i2c1_tx.Init.Priority = DMA_PRIORITY_LOW; ++ hdma_i2c1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; ++ if (HAL_DMA_Init(&hdma_i2c1_tx) != HAL_OK) ++ { ++ _Error_Handler(__FILE__, __LINE__); ++ } ++ ++ __HAL_LINKDMA(i2cHandle,hdmatx,hdma_i2c1_tx); ++ ++ /* I2C1 interrupt Init */ ++ HAL_NVIC_SetPriority(I2C1_EV_IRQn, 5, 0); ++ HAL_NVIC_EnableIRQ(I2C1_EV_IRQn); ++ HAL_NVIC_SetPriority(I2C1_ER_IRQn, 5, 0); ++ HAL_NVIC_EnableIRQ(I2C1_ER_IRQn); ++ /* USER CODE BEGIN I2C1_MspInit 1 */ ++ ++ /* USER CODE END I2C1_MspInit 1 */ ++ } ++} ++ ++void HAL_I2C_MspDeInit(I2C_HandleTypeDef* i2cHandle) ++{ ++ ++ if(i2cHandle->Instance==I2C1) ++ { ++ /* USER CODE BEGIN I2C1_MspDeInit 0 */ ++ ++ /* USER CODE END I2C1_MspDeInit 0 */ ++ /* Peripheral clock disable */ ++ __HAL_RCC_I2C1_CLK_DISABLE(); ++ ++ /**I2C1 GPIO Configuration ++ PB8 ------> I2C1_SCL ++ PB9 ------> I2C1_SDA ++ */ ++ HAL_GPIO_DeInit(GPIOB, GPIO_PIN_8|GPIO_PIN_9); ++ ++ /* I2C1 DMA DeInit */ ++ HAL_DMA_DeInit(i2cHandle->hdmarx); ++ HAL_DMA_DeInit(i2cHandle->hdmatx); ++ ++ /* I2C1 interrupt Deinit */ ++ HAL_NVIC_DisableIRQ(I2C1_EV_IRQn); ++ HAL_NVIC_DisableIRQ(I2C1_ER_IRQn); ++ /* USER CODE BEGIN I2C1_MspDeInit 1 */ ++ ++ /* USER CODE END I2C1_MspDeInit 1 */ ++ } ++} ++ ++/* USER CODE BEGIN 1 */ ++ ++/* USER CODE END 1 */ ++ ++/** ++ * @} ++ */ ++ ++/** ++ * @} ++ */ ++ ++/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ +diff --git a/Firmware/Board/v3/Src/main.c b/Firmware/Board/v3/Src/main.c +index 987e0bd..efaeef1 100644 +--- a/Firmware/Board/v3/Src/main.c ++++ b/Firmware/Board/v3/Src/main.c +@@ -187,7 +187,6 @@ int main(void) + MX_ADC3_Init(); + MX_TIM2_Init(); + MX_UART4_Init(); +- MX_CAN1_Init(); + /* USER CODE BEGIN 2 */ + + //Required to use OC4 for ADC triggering. +diff --git a/Firmware/Board/v3/Src/stm32f4xx_it.c b/Firmware/Board/v3/Src/stm32f4xx_it.c +index 8434a9e..f6b4fcd 100644 +--- a/Firmware/Board/v3/Src/stm32f4xx_it.c ++++ b/Firmware/Board/v3/Src/stm32f4xx_it.c +@@ -54,6 +54,9 @@ extern PCD_HandleTypeDef hpcd_USB_OTG_FS; + extern ADC_HandleTypeDef hadc1; + extern ADC_HandleTypeDef hadc2; + extern ADC_HandleTypeDef hadc3; ++extern DMA_HandleTypeDef hdma_i2c1_rx; ++extern DMA_HandleTypeDef hdma_i2c1_tx; ++extern I2C_HandleTypeDef hi2c1; + extern TIM_HandleTypeDef htim8; + extern DMA_HandleTypeDef hdma_uart4_rx; + extern DMA_HandleTypeDef hdma_uart4_tx; +@@ -266,6 +269,34 @@ void ADC_IRQHandler(void) + /* USER CODE END ADC_IRQn 1 */ + } + ++/** ++* @brief This function handles I2C1 event interrupt. ++*/ ++void I2C1_EV_IRQHandler(void) ++{ ++ /* USER CODE BEGIN I2C1_EV_IRQn 0 */ ++ ++ /* USER CODE END I2C1_EV_IRQn 0 */ ++ HAL_I2C_EV_IRQHandler(&hi2c1); ++ /* USER CODE BEGIN I2C1_EV_IRQn 1 */ ++ ++ /* USER CODE END I2C1_EV_IRQn 1 */ ++} ++ ++/** ++* @brief This function handles I2C1 error interrupt. ++*/ ++void I2C1_ER_IRQHandler(void) ++{ ++ /* USER CODE BEGIN I2C1_ER_IRQn 0 */ ++ ++ /* USER CODE END I2C1_ER_IRQn 0 */ ++ HAL_I2C_ER_IRQHandler(&hi2c1); ++ /* USER CODE BEGIN I2C1_ER_IRQn 1 */ ++ ++ /* USER CODE END I2C1_ER_IRQn 1 */ ++} ++ + /** + * @brief This function handles TIM8 trigger and commutation interrupts and TIM14 global interrupt. + */ +-- +2.17.0 + From cb6f2d514329a7d09079364477e6c0cf732b9954 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 26 Apr 2018 13:34:38 -0700 Subject: [PATCH 145/215] use idle hook --- Firmware/Board/v3/Odrive.ioc | 8 ++------ Firmware/Board/v3/Src/freertos.c | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Firmware/Board/v3/Odrive.ioc b/Firmware/Board/v3/Odrive.ioc index de9fdc2c..7da71a60 100644 --- a/Firmware/Board/v3/Odrive.ioc +++ b/Firmware/Board/v3/Odrive.ioc @@ -126,13 +126,12 @@ Dma.UART4_TX.1.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataA FREERTOS.FootprintOK=true FREERTOS.INCLUDE_uxTaskGetStackHighWaterMark=1 FREERTOS.INCLUDE_vTaskDelayUntil=1 -FREERTOS.IPParameters=Tasks01,INCLUDE_vTaskDelayUntil,configTOTAL_HEAP_SIZE,FootprintOK,configCHECK_FOR_STACK_OVERFLOW,INCLUDE_uxTaskGetStackHighWaterMark +FREERTOS.IPParameters=Tasks01,INCLUDE_vTaskDelayUntil,configTOTAL_HEAP_SIZE,FootprintOK,configCHECK_FOR_STACK_OVERFLOW,INCLUDE_uxTaskGetStackHighWaterMark,configUSE_IDLE_HOOK FREERTOS.Tasks01=defaultTask,0,256,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL FREERTOS.configCHECK_FOR_STACK_OVERFLOW=1 FREERTOS.configTOTAL_HEAP_SIZE=65536 +FREERTOS.configUSE_IDLE_HOOK=1 File.Version=6 -I2C1.IPParameters=OwnAddress -I2C1.OwnAddress=12 KeepUserPlacement=true Mcu.Family=STM32F4 Mcu.IP0=ADC1 @@ -145,7 +144,6 @@ Mcu.IP14=TIM8 Mcu.IP15=UART4 Mcu.IP16=USB_DEVICE Mcu.IP17=USB_OTG_FS -Mcu.IP18=I2C1 Mcu.IP2=ADC3 Mcu.IP3=CAN1 Mcu.IP4=DMA @@ -227,8 +225,6 @@ NVIC.DMA1_Stream4_IRQn=true\:5\:0\:false\:false\:true\:true\:false NVIC.DMA1_Stream6_IRQn=true\:5\:0\:false\:false\:true\:true\:false NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:true NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:true -NVIC.I2C1_ER_IRQn=true\:5\:0\:false\:false\:true\:true\:true -NVIC.I2C1_EV_IRQn=true\:5\:0\:false\:false\:true\:true\:true NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:true NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:true NVIC.OTG_FS_IRQn=true\:5\:0\:false\:false\:true\:true\:true diff --git a/Firmware/Board/v3/Src/freertos.c b/Firmware/Board/v3/Src/freertos.c index f1d46642..524cd6cb 100644 --- a/Firmware/Board/v3/Src/freertos.c +++ b/Firmware/Board/v3/Src/freertos.c @@ -86,8 +86,24 @@ void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */ /* USER CODE END FunctionPrototypes */ /* Hook prototypes */ +void vApplicationIdleHook(void); void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName); +/* USER CODE BEGIN 2 */ +__weak void vApplicationIdleHook( void ) +{ + /* vApplicationIdleHook() will only be called if configUSE_IDLE_HOOK is set + to 1 in FreeRTOSConfig.h. It will be called on each iteration of the idle + task. It is essential that code added to this hook function never attempts + to block in any way (for example, call xQueueReceive() with a block time + specified, or call vTaskDelay()). If the application makes use of the + vTaskDelete() API function (as this demo application does) then it is also + important that vApplicationIdleHook() is permitted to return to its calling + function, because it is the responsibility of the idle task to clean up + memory allocated by the kernel to any task that has since been deleted. */ +} +/* USER CODE END 2 */ + /* USER CODE BEGIN 4 */ __weak void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName) { From 4420d3873aea0be6d15c4543bd11608eb83fce5e Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 23 Apr 2018 14:31:12 -0700 Subject: [PATCH 146/215] lowish current motor resistance meas config --- Firmware/MotorControl/motor.cpp | 3 ++- Firmware/MotorControl/motor.hpp | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 3272c746..cbf62952 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -78,7 +78,8 @@ void Motor::DRV8301_setup() { // 40V/V on 500uOhm gives a range of +/- 75A // 20V/V on 666uOhm gives a range of +/- 110A // 40V/V on 666uOhm gives a range of +/- 55A - local_regs->Ctrl_Reg_2.GAIN = DRV8301_ShuntAmpGain_40VpV; + local_regs->Ctrl_Reg_2.GAIN = DRV8301_ShuntAmpGain_80VpV; + // local_regs->Ctrl_Reg_2.GAIN = DRV8301_ShuntAmpGain_40VpV; // local_regs->Ctrl_Reg_2.GAIN = DRV8301_ShuntAmpGain_20VpV; switch (local_regs->Ctrl_Reg_2.GAIN) { diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 950141bb..74679b88 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -37,9 +37,9 @@ typedef struct { // example: current_lim and calibration_current will instead determine the maximum voltage applied to the motor. typedef struct { bool pre_calibrated = false; // can be set to true to indicate that all values here are valid - int32_t pole_pairs = 7; // This value is correct for N5065 motors and Turnigy SK3 series. - float calibration_current = 10.0f; // [A] - float resistance_calib_max_voltage = 1.0f; // [V] - You may need to increase this if this voltage isn't sufficient to drive calibration_current through the motor. + int32_t pole_pairs = 12; + float calibration_current = 6.0f; // [A] + float resistance_calib_max_voltage = 2.0f; // [V] - You may need to increase this if this voltage isn't sufficient to drive calibration_current through the motor. float phase_inductance = 0.0f; // to be set by measure_phase_inductance float phase_resistance = 0.0f; // to be set by measure_phase_resistance int32_t direction = 1; // 1 or -1 From 7d6ddc96b2b7c327031cfe6365a2571070601a87 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 26 Apr 2018 11:23:23 -0700 Subject: [PATCH 147/215] update encoder during calib --- Firmware/MotorControl/encoder.cpp | 2 ++ Firmware/MotorControl/motor.cpp | 4 ++++ Firmware/MotorControl/motor.hpp | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 6267e152..168bc0e0 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -148,6 +148,8 @@ bool Encoder::run_offset_calibration() { // scan forward i = 0; axis_->run_control_loop([&](){ + axis_->encoder_.update(nullptr, nullptr, nullptr); + float phase = wrap_pm_pi(scan_distance * (float)i / (float)num_steps - scan_distance / 2.0f); float v_alpha = voltage_magnitude * arm_cos_f32(phase); float v_beta = voltage_magnitude * arm_sin_f32(phase); diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index cbf62952..dad64bc5 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -167,6 +167,8 @@ bool Motor::measure_phase_resistance(float test_current, float max_voltage) { size_t i = 0; axis_->run_control_loop([&](){ + axis_->encoder_.update(nullptr, nullptr, nullptr); + float Ialpha = -(current_meas_.phB + current_meas_.phC); test_voltage += (kI * current_meas_period) * (test_current - Ialpha); if (test_voltage > max_voltage || test_voltage < -max_voltage) @@ -198,6 +200,8 @@ bool Motor::measure_phase_inductance(float voltage_low, float voltage_high) { size_t t = 0; axis_->run_control_loop([&](){ + axis_->encoder_.update(nullptr, nullptr, nullptr); + int i = t & 1; Ialphas[i] += -current_meas_.phB - current_meas_.phC; diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 74679b88..49ca126b 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -48,7 +48,7 @@ typedef struct { // Read out max_allowed_current to see max supported value for current_lim. // You can change DRV8301_ShuntAmpGain to get a different range. // float current_lim = 75.0f; //[A] - float current_lim = 10.0f; //[A] + float current_lim = 6.0f; //[A] } MotorConfig_t; class Motor { From 5518d1e12dce5dfd20bb9f82c5d21a5b46266e15 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 26 Apr 2018 13:45:33 -0700 Subject: [PATCH 148/215] add hall sample space --- Firmware/MotorControl/axis.hpp | 2 +- Firmware/MotorControl/encoder.hpp | 3 ++ Firmware/MotorControl/low_level.cpp | 39 ++++++++++++------------ Firmware/MotorControl/low_level.h | 3 +- Firmware/MotorControl/motor.hpp | 17 ++++++----- Firmware/MotorControl/odrive_main.h | 5 ++- Firmware/communication/communication.cpp | 2 +- 7 files changed, 38 insertions(+), 33 deletions(-) diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 8e2245d4..e5d10fa4 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -102,7 +102,7 @@ public: template void run_control_loop(const T& update_handler) { while (requested_state_ == AXIS_STATE_UNDEFINED) { - if (!brake_resistor_armed_) { + if (!brake_resistor_armed) { error_ |= ERROR_BRAKE_RESISTOR_DISARMED; break; } diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 9a59654e..74836fad 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -61,6 +61,9 @@ public: float pll_kp_ = 0.0f; // [rad/s / rad] float pll_ki_ = 0.0f; // [(rad/s^2) / rad] + // Updated by low_level pwm_adc_cb + bool hallA_ = 0, hallB_ = 0, hallC_ = 0; + // Communication protocol definitions auto make_protocol_definitions() { return make_protocol_member_list( diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index a984b789..07061468 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -29,14 +29,16 @@ /* Private typedef -----------------------------------------------------------*/ /* Global constant data ------------------------------------------------------*/ /* Global variables ----------------------------------------------------------*/ + // This value is updated by the DC-bus reading ADC. // Arbitrary non-zero inital value to avoid division by zero if ADC reading is late float vbus_voltage = 12.0f; -bool brake_resistor_armed_ = false; - +bool brake_resistor_armed = false; /* Private constant data -----------------------------------------------------*/ /* Private variables ---------------------------------------------------------*/ +// Two motors, sampling port A,B,C (coherent with current meas timing) +static uint16_t GPIO_port_samples [2][3]; /* CPU critical section helpers ----------------------------------------------*/ static inline uint8_t cpu_enter_critical() { @@ -94,13 +96,23 @@ static inline void cpu_exit_critical(uint8_t status_register) { * at a high rate. */ +// @brief Floats ALL phases immediately and disarms both motors and the brake resistor. +void low_level_fault(Motor::Error_t error) { + // Disable all motors NOW! + for (size_t i = 0; i < AXIS_COUNT; ++i) { + safety_critical_disarm_motor_pwm(axes[i]->motor_); + axes[i]->motor_.error_ |= error; + } + + safety_critical_disarm_brake_resistor(); +} // @brief Kicks off the arming process of the motor. // All calls to this function must clearly originate // from user input. void safety_critical_arm_motor_pwm(Motor& motor) { uint8_t sr = cpu_enter_critical(); - if (brake_resistor_armed_) { + if (brake_resistor_armed) { motor.armed_state_ = Motor::ARMED_STATE_WAITING_FOR_TIMINGS; } cpu_exit_critical(sr); @@ -127,7 +139,7 @@ bool safety_critical_disarm_motor_pwm(Motor& motor) { // timer period. void safety_critical_apply_motor_pwm_timings(Motor& motor, uint16_t timings[3]) { uint8_t sr = cpu_enter_critical(); - if (!brake_resistor_armed_) { + if (!brake_resistor_armed) { motor.armed_state_ = Motor::ARMED_STATE_ARMED; } @@ -158,7 +170,7 @@ void safety_critical_apply_motor_pwm_timings(Motor& motor, uint16_t timings[3]) // @brief Arms the brake resistor void safety_critical_arm_brake_resistor() { uint8_t sr = cpu_enter_critical(); - brake_resistor_armed_ = true; + brake_resistor_armed = true; htim2.Instance->CCR3 = 0; htim2.Instance->CCR4 = TIM_APB1_PERIOD_CLOCKS + 1; cpu_exit_critical(sr); @@ -170,7 +182,7 @@ void safety_critical_arm_brake_resistor() { // by calling safety_critical_arm_brake_resistor(). void safety_critical_disarm_brake_resistor() { uint8_t sr = cpu_enter_critical(); - brake_resistor_armed_ = false; + brake_resistor_armed = false; htim2.Instance->CCR3 = 0; htim2.Instance->CCR4 = TIM_APB1_PERIOD_CLOCKS + 1; for (size_t i = 0; i < AXIS_COUNT; ++i) { @@ -183,9 +195,9 @@ void safety_critical_disarm_brake_resistor() { // the brake resistor is disarmed. void safety_critical_apply_brake_resistor_timings(uint32_t low_off, uint32_t high_on) { if (high_on - low_off < TIM_APB1_DEADTIME_CLOCKS) - for(;;); + low_level_fault(Motor::ERROR_BRAKE_DEADTIME_VIOLATION); uint8_t sr = cpu_enter_critical(); - if (brake_resistor_armed_) { + if (brake_resistor_armed) { // Safe update of low and high side timings // To avoid race condition, first reset timings to safe state // ch3 is low side, ch4 is high side @@ -303,17 +315,6 @@ void sync_timers(TIM_HandleTypeDef* htim_a, TIM_HandleTypeDef* htim_b, htim_b->Instance->BDTR |= MOE_store_b; } -// @brief Floats ALL phases immediately and disarms both motors and the brake resistor. -void low_level_fault(Motor::Error_t error) { - // Disable all motors NOW! - for (size_t i = 0; i < AXIS_COUNT; ++i) { - safety_critical_disarm_motor_pwm(axes[i]->motor_); - axes[i]->motor_.error_ |= error; - } - - safety_critical_disarm_brake_resistor(); -} - //-------------------------------- // IRQ Callbacks //-------------------------------- diff --git a/Firmware/MotorControl/low_level.h b/Firmware/MotorControl/low_level.h index e3784788..98611877 100644 --- a/Firmware/MotorControl/low_level.h +++ b/Firmware/MotorControl/low_level.h @@ -18,6 +18,8 @@ extern "C" { /* Exported types ------------------------------------------------------------*/ /* Exported constants --------------------------------------------------------*/ /* Exported variables --------------------------------------------------------*/ +extern float vbus_voltage; +extern bool brake_resistor_armed; /* Exported macro ------------------------------------------------------------*/ /* Exported functions --------------------------------------------------------*/ @@ -39,7 +41,6 @@ void start_adc_pwm(); void start_pwm(TIM_HandleTypeDef* htim); void sync_timers(TIM_HandleTypeDef* htim_a, TIM_HandleTypeDef* htim_b, uint16_t TIM_CLOCKSOURCE_ITRx, uint16_t count_offset); - void update_brake_current(); #ifdef __cplusplus diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 49ca126b..f203cddc 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -55,14 +55,15 @@ class Motor { public: enum Error_t { ERROR_NO_ERROR = 0, - ERROR_PHASE_RESISTANCE_OUT_OF_RANGE = 0x01, - ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE = 0x02, - ERROR_ADC_FAILED = 0x04, - ERROR_DRV_FAULT = 0x08, - ERROR_CONTROL_DEADLINE_MISSED = 0x10, - ERROR_NOT_IMPLEMENTED_MOTOR_TYPE = 0x20, - ERROR_BRAKE_CURRENT_OUT_OF_RANGE = 0x40, - ERROR_NUMERICAL = 0x80 + ERROR_PHASE_RESISTANCE_OUT_OF_RANGE = 0x0001, + ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE = 0x0002, + ERROR_ADC_FAILED = 0x0004, + ERROR_DRV_FAULT = 0x0008, + ERROR_CONTROL_DEADLINE_MISSED = 0x0010, + ERROR_NOT_IMPLEMENTED_MOTOR_TYPE = 0x0020, + ERROR_BRAKE_CURRENT_OUT_OF_RANGE = 0x0040, + ERROR_NUMERICAL = 0x0080, + ERROR_BRAKE_DEADTIME_VIOLATION = 0x0100 }; enum TimingLog_t { diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 866caa4a..4228720a 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -25,11 +25,10 @@ extern "C" { //default timeout waiting for phase measurement signals #define PH_CURRENT_MEAS_TIMEOUT 2 // [ms] +//TODO clean this up static const float current_meas_period = CURRENT_MEAS_PERIOD; static const int current_meas_hz = CURRENT_MEAS_HZ; -extern float vbus_voltage; -extern bool brake_resistor_armed_; -extern const float elec_rad_per_enc; +// extern const float elec_rad_per_enc; extern uint32_t _reboot_cookie; extern bool user_config_loaded_; diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index a0234a3f..272c0d28 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -111,7 +111,7 @@ static inline auto make_obj_tree() { make_protocol_ro_property("fw_version_revision", &fw_version_revision), make_protocol_ro_property("fw_version_unreleased", &fw_version_unreleased), make_protocol_ro_property("user_config_loaded", const_cast(&user_config_loaded_)), - make_protocol_ro_property("brake_resistor_armed", &brake_resistor_armed_), + make_protocol_ro_property("brake_resistor_armed", &brake_resistor_armed), make_protocol_object("system_stats", make_protocol_ro_property("uptime", &system_stats_.uptime), make_protocol_ro_property("min_heap_space", &system_stats_.min_heap_space), From ea1d3771dcff2351b6cc4ad32cf0e000ae620b74 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 26 Apr 2018 14:33:52 -0700 Subject: [PATCH 149/215] remove I2C handler from patch file and stick in user section instead --- .../v3/0002-Add-I2C-files-and-settings.patch | 63 ------------------- Firmware/Board/v3/Inc/stm32f4xx_it.h | 1 - Firmware/Board/v3/Src/stm32f4xx_it.c | 48 +++++--------- 3 files changed, 17 insertions(+), 95 deletions(-) diff --git a/Firmware/Board/v3/0002-Add-I2C-files-and-settings.patch b/Firmware/Board/v3/0002-Add-I2C-files-and-settings.patch index e1b7e714..aae18bf4 100644 --- a/Firmware/Board/v3/0002-Add-I2C-files-and-settings.patch +++ b/Firmware/Board/v3/0002-Add-I2C-files-and-settings.patch @@ -10,11 +10,9 @@ Subject: [PATCH] Add I2C files and settings .../Src/stm32f4xx_hal_i2c_ex.c | 204 + Firmware/Board/v3/Inc/i2c.h | 91 + Firmware/Board/v3/Inc/stm32f4xx_hal_conf.h | 2 +- - Firmware/Board/v3/Inc/stm32f4xx_it.h | 1 + Firmware/Board/v3/Odrive.ioc | 5 + Firmware/Board/v3/Src/i2c.c | 198 + Firmware/Board/v3/Src/main.c | 1 - - Firmware/Board/v3/Src/stm32f4xx_it.c | 31 + 11 files changed, 6811 insertions(+), 2 deletions(-) create mode 100644 Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c.h create mode 100644 Firmware/Board/v3/Drivers/STM32F4xx_HAL_Driver/Inc/stm32f4xx_hal_i2c_ex.h @@ -6641,18 +6639,6 @@ index d0f48f5..b3ef59a 100644 /* #define HAL_I2S_MODULE_ENABLED */ /* #define HAL_IWDG_MODULE_ENABLED */ /* #define HAL_LTDC_MODULE_ENABLED */ -diff --git a/Firmware/Board/v3/Inc/stm32f4xx_it.h b/Firmware/Board/v3/Inc/stm32f4xx_it.h -index a4d1813..050334c 100644 ---- a/Firmware/Board/v3/Inc/stm32f4xx_it.h -+++ b/Firmware/Board/v3/Inc/stm32f4xx_it.h -@@ -59,6 +59,7 @@ void DMA1_Stream2_IRQHandler(void); - void DMA1_Stream4_IRQHandler(void); - void DMA1_Stream6_IRQHandler(void); - void ADC_IRQHandler(void); -+void I2C1_ER_IRQHandler(void); - void TIM8_TRG_COM_TIM14_IRQHandler(void); - void UART4_IRQHandler(void); - void OTG_FS_IRQHandler(void); diff --git a/Firmware/Board/v3/Src/i2c.c b/Firmware/Board/v3/Src/i2c.c new file mode 100644 index 0000000..bae77f1 @@ -6869,55 +6855,6 @@ index 987e0bd..efaeef1 100644 /* USER CODE BEGIN 2 */ //Required to use OC4 for ADC triggering. -diff --git a/Firmware/Board/v3/Src/stm32f4xx_it.c b/Firmware/Board/v3/Src/stm32f4xx_it.c -index 8434a9e..f6b4fcd 100644 ---- a/Firmware/Board/v3/Src/stm32f4xx_it.c -+++ b/Firmware/Board/v3/Src/stm32f4xx_it.c -@@ -54,6 +54,9 @@ extern PCD_HandleTypeDef hpcd_USB_OTG_FS; - extern ADC_HandleTypeDef hadc1; - extern ADC_HandleTypeDef hadc2; - extern ADC_HandleTypeDef hadc3; -+extern DMA_HandleTypeDef hdma_i2c1_rx; -+extern DMA_HandleTypeDef hdma_i2c1_tx; -+extern I2C_HandleTypeDef hi2c1; - extern TIM_HandleTypeDef htim8; - extern DMA_HandleTypeDef hdma_uart4_rx; - extern DMA_HandleTypeDef hdma_uart4_tx; -@@ -266,6 +269,34 @@ void ADC_IRQHandler(void) - /* USER CODE END ADC_IRQn 1 */ - } - -+/** -+* @brief This function handles I2C1 event interrupt. -+*/ -+void I2C1_EV_IRQHandler(void) -+{ -+ /* USER CODE BEGIN I2C1_EV_IRQn 0 */ -+ -+ /* USER CODE END I2C1_EV_IRQn 0 */ -+ HAL_I2C_EV_IRQHandler(&hi2c1); -+ /* USER CODE BEGIN I2C1_EV_IRQn 1 */ -+ -+ /* USER CODE END I2C1_EV_IRQn 1 */ -+} -+ -+/** -+* @brief This function handles I2C1 error interrupt. -+*/ -+void I2C1_ER_IRQHandler(void) -+{ -+ /* USER CODE BEGIN I2C1_ER_IRQn 0 */ -+ -+ /* USER CODE END I2C1_ER_IRQn 0 */ -+ HAL_I2C_ER_IRQHandler(&hi2c1); -+ /* USER CODE BEGIN I2C1_ER_IRQn 1 */ -+ -+ /* USER CODE END I2C1_ER_IRQn 1 */ -+} -+ - /** - * @brief This function handles TIM8 trigger and commutation interrupts and TIM14 global interrupt. - */ -- 2.17.0 diff --git a/Firmware/Board/v3/Inc/stm32f4xx_it.h b/Firmware/Board/v3/Inc/stm32f4xx_it.h index 050334c4..a4d18137 100644 --- a/Firmware/Board/v3/Inc/stm32f4xx_it.h +++ b/Firmware/Board/v3/Inc/stm32f4xx_it.h @@ -59,7 +59,6 @@ void DMA1_Stream2_IRQHandler(void); void DMA1_Stream4_IRQHandler(void); void DMA1_Stream6_IRQHandler(void); void ADC_IRQHandler(void); -void I2C1_ER_IRQHandler(void); void TIM8_TRG_COM_TIM14_IRQHandler(void); void UART4_IRQHandler(void); void OTG_FS_IRQHandler(void); diff --git a/Firmware/Board/v3/Src/stm32f4xx_it.c b/Firmware/Board/v3/Src/stm32f4xx_it.c index f6b4fcde..c4eed53e 100644 --- a/Firmware/Board/v3/Src/stm32f4xx_it.c +++ b/Firmware/Board/v3/Src/stm32f4xx_it.c @@ -47,6 +47,8 @@ void ADC_IRQ_Dispatch(ADC_HandleTypeDef* hadc, ADC_handler_t callback); void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected); void vbus_sense_adc_cb(ADC_HandleTypeDef* hadc, bool injected); +extern I2C_HandleTypeDef hi2c1; + /* USER CODE END 0 */ /* External variables --------------------------------------------------------*/ @@ -54,9 +56,6 @@ extern PCD_HandleTypeDef hpcd_USB_OTG_FS; extern ADC_HandleTypeDef hadc1; extern ADC_HandleTypeDef hadc2; extern ADC_HandleTypeDef hadc3; -extern DMA_HandleTypeDef hdma_i2c1_rx; -extern DMA_HandleTypeDef hdma_i2c1_tx; -extern I2C_HandleTypeDef hi2c1; extern TIM_HandleTypeDef htim8; extern DMA_HandleTypeDef hdma_uart4_rx; extern DMA_HandleTypeDef hdma_uart4_tx; @@ -269,34 +268,6 @@ void ADC_IRQHandler(void) /* USER CODE END ADC_IRQn 1 */ } -/** -* @brief This function handles I2C1 event interrupt. -*/ -void I2C1_EV_IRQHandler(void) -{ - /* USER CODE BEGIN I2C1_EV_IRQn 0 */ - - /* USER CODE END I2C1_EV_IRQn 0 */ - HAL_I2C_EV_IRQHandler(&hi2c1); - /* USER CODE BEGIN I2C1_EV_IRQn 1 */ - - /* USER CODE END I2C1_EV_IRQn 1 */ -} - -/** -* @brief This function handles I2C1 error interrupt. -*/ -void I2C1_ER_IRQHandler(void) -{ - /* USER CODE BEGIN I2C1_ER_IRQn 0 */ - - /* USER CODE END I2C1_ER_IRQn 0 */ - HAL_I2C_ER_IRQHandler(&hi2c1); - /* USER CODE BEGIN I2C1_ER_IRQn 1 */ - - /* USER CODE END I2C1_ER_IRQn 1 */ -} - /** * @brief This function handles TIM8 trigger and commutation interrupts and TIM14 global interrupt. */ @@ -367,6 +338,21 @@ void ADC_IRQ_Dispatch(ADC_HandleTypeDef* hadc, ADC_handler_t callback) { } } +/** +* @brief This function handles I2C1 event interrupt. +*/ +void I2C1_EV_IRQHandler(void) +{ + HAL_I2C_EV_IRQHandler(&hi2c1); +} + +/** +* @brief This function handles I2C1 error interrupt. +*/ +void I2C1_ER_IRQHandler(void) +{ + HAL_I2C_ER_IRQHandler(&hi2c1); +} /** * @brief This function handles EXTI line0 interrupt. From 0932527401920aa0f3e97e867346a13b00662892 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 26 Apr 2018 14:38:37 -0700 Subject: [PATCH 150/215] clean away I2C DMA streams --- Firmware/Board/v3/Inc/stm32f4xx_it.h | 2 -- Firmware/Board/v3/Odrive.ioc | 26 +------------------------- Firmware/Board/v3/Src/dma.c | 6 ------ Firmware/Board/v3/Src/stm32f4xx_it.c | 28 ---------------------------- 4 files changed, 1 insertion(+), 61 deletions(-) diff --git a/Firmware/Board/v3/Inc/stm32f4xx_it.h b/Firmware/Board/v3/Inc/stm32f4xx_it.h index a4d18137..1970c2a8 100644 --- a/Firmware/Board/v3/Inc/stm32f4xx_it.h +++ b/Firmware/Board/v3/Inc/stm32f4xx_it.h @@ -54,10 +54,8 @@ void BusFault_Handler(void); void UsageFault_Handler(void); void DebugMon_Handler(void); void SysTick_Handler(void); -void DMA1_Stream0_IRQHandler(void); void DMA1_Stream2_IRQHandler(void); void DMA1_Stream4_IRQHandler(void); -void DMA1_Stream6_IRQHandler(void); void ADC_IRQHandler(void); void TIM8_TRG_COM_TIM14_IRQHandler(void); void UART4_IRQHandler(void); diff --git a/Firmware/Board/v3/Odrive.ioc b/Firmware/Board/v3/Odrive.ioc index 7da71a60..21d9f52f 100644 --- a/Firmware/Board/v3/Odrive.ioc +++ b/Firmware/Board/v3/Odrive.ioc @@ -78,31 +78,9 @@ ADC3.ScanConvMode=DISABLE CAN1.CalculateTimeBit=1142 CAN1.CalculateTimeQuantum=380.95238095238096 CAN1.IPParameters=CalculateTimeQuantum,CalculateTimeBit -Dma.I2C1_RX.2.Direction=DMA_PERIPH_TO_MEMORY -Dma.I2C1_RX.2.FIFOMode=DMA_FIFOMODE_DISABLE -Dma.I2C1_RX.2.Instance=DMA1_Stream0 -Dma.I2C1_RX.2.MemDataAlignment=DMA_MDATAALIGN_BYTE -Dma.I2C1_RX.2.MemInc=DMA_MINC_ENABLE -Dma.I2C1_RX.2.Mode=DMA_NORMAL -Dma.I2C1_RX.2.PeriphDataAlignment=DMA_PDATAALIGN_BYTE -Dma.I2C1_RX.2.PeriphInc=DMA_PINC_DISABLE -Dma.I2C1_RX.2.Priority=DMA_PRIORITY_LOW -Dma.I2C1_RX.2.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode -Dma.I2C1_TX.3.Direction=DMA_MEMORY_TO_PERIPH -Dma.I2C1_TX.3.FIFOMode=DMA_FIFOMODE_DISABLE -Dma.I2C1_TX.3.Instance=DMA1_Stream6 -Dma.I2C1_TX.3.MemDataAlignment=DMA_MDATAALIGN_BYTE -Dma.I2C1_TX.3.MemInc=DMA_MINC_ENABLE -Dma.I2C1_TX.3.Mode=DMA_NORMAL -Dma.I2C1_TX.3.PeriphDataAlignment=DMA_PDATAALIGN_BYTE -Dma.I2C1_TX.3.PeriphInc=DMA_PINC_DISABLE -Dma.I2C1_TX.3.Priority=DMA_PRIORITY_LOW -Dma.I2C1_TX.3.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode Dma.Request0=UART4_RX Dma.Request1=UART4_TX -Dma.Request2=I2C1_RX -Dma.Request3=I2C1_TX -Dma.RequestsNb=4 +Dma.RequestsNb=2 Dma.UART4_RX.0.Direction=DMA_PERIPH_TO_MEMORY Dma.UART4_RX.0.FIFOMode=DMA_FIFOMODE_DISABLE Dma.UART4_RX.0.Instance=DMA1_Stream2 @@ -219,10 +197,8 @@ MxCube.Version=4.24.0 MxDb.Version=DB.4.0.240 NVIC.ADC_IRQn=true\:5\:0\:false\:false\:true\:true\:true NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:true -NVIC.DMA1_Stream0_IRQn=true\:5\:0\:false\:false\:true\:true\:false NVIC.DMA1_Stream2_IRQn=true\:5\:0\:false\:false\:true\:true\:true NVIC.DMA1_Stream4_IRQn=true\:5\:0\:false\:false\:true\:true\:false -NVIC.DMA1_Stream6_IRQn=true\:5\:0\:false\:false\:true\:true\:false NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:true NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:true NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:true diff --git a/Firmware/Board/v3/Src/dma.c b/Firmware/Board/v3/Src/dma.c index 8ebfe19e..a725a585 100644 --- a/Firmware/Board/v3/Src/dma.c +++ b/Firmware/Board/v3/Src/dma.c @@ -70,18 +70,12 @@ void MX_DMA_Init(void) __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA interrupt init */ - /* DMA1_Stream0_IRQn interrupt configuration */ - HAL_NVIC_SetPriority(DMA1_Stream0_IRQn, 5, 0); - HAL_NVIC_EnableIRQ(DMA1_Stream0_IRQn); /* DMA1_Stream2_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Stream2_IRQn, 5, 0); HAL_NVIC_EnableIRQ(DMA1_Stream2_IRQn); /* DMA1_Stream4_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 5, 0); HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn); - /* DMA1_Stream6_IRQn interrupt configuration */ - HAL_NVIC_SetPriority(DMA1_Stream6_IRQn, 5, 0); - HAL_NVIC_EnableIRQ(DMA1_Stream6_IRQn); } diff --git a/Firmware/Board/v3/Src/stm32f4xx_it.c b/Firmware/Board/v3/Src/stm32f4xx_it.c index c4eed53e..2322c0d7 100644 --- a/Firmware/Board/v3/Src/stm32f4xx_it.c +++ b/Firmware/Board/v3/Src/stm32f4xx_it.c @@ -186,20 +186,6 @@ void SysTick_Handler(void) /* please refer to the startup file (startup_stm32f4xx.s). */ /******************************************************************************/ -/** -* @brief This function handles DMA1 stream0 global interrupt. -*/ -void DMA1_Stream0_IRQHandler(void) -{ - /* USER CODE BEGIN DMA1_Stream0_IRQn 0 */ - - /* USER CODE END DMA1_Stream0_IRQn 0 */ - HAL_DMA_IRQHandler(&hdma_i2c1_rx); - /* USER CODE BEGIN DMA1_Stream0_IRQn 1 */ - - /* USER CODE END DMA1_Stream0_IRQn 1 */ -} - /** * @brief This function handles DMA1 stream2 global interrupt. */ @@ -228,20 +214,6 @@ void DMA1_Stream4_IRQHandler(void) /* USER CODE END DMA1_Stream4_IRQn 1 */ } -/** -* @brief This function handles DMA1 stream6 global interrupt. -*/ -void DMA1_Stream6_IRQHandler(void) -{ - /* USER CODE BEGIN DMA1_Stream6_IRQn 0 */ - - /* USER CODE END DMA1_Stream6_IRQn 0 */ - HAL_DMA_IRQHandler(&hdma_i2c1_tx); - /* USER CODE BEGIN DMA1_Stream6_IRQn 1 */ - - /* USER CODE END DMA1_Stream6_IRQn 1 */ -} - /** * @brief This function handles ADC1, ADC2 and ADC3 global interrupts. */ From c09682efc8664eabb21de4c9048f460c127e1bd6 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 26 Apr 2018 14:41:36 -0700 Subject: [PATCH 151/215] TIM1 and TIM8 interrupt enable --- Firmware/Board/v3/Inc/stm32f4xx_it.h | 2 ++ Firmware/Board/v3/Odrive.ioc | 2 ++ Firmware/Board/v3/Src/stm32f4xx_it.c | 29 ++++++++++++++++++++++++++++ Firmware/Board/v3/Src/tim.c | 10 ++++++++++ 4 files changed, 43 insertions(+) diff --git a/Firmware/Board/v3/Inc/stm32f4xx_it.h b/Firmware/Board/v3/Inc/stm32f4xx_it.h index 1970c2a8..7dbded4e 100644 --- a/Firmware/Board/v3/Inc/stm32f4xx_it.h +++ b/Firmware/Board/v3/Inc/stm32f4xx_it.h @@ -57,6 +57,8 @@ void SysTick_Handler(void); void DMA1_Stream2_IRQHandler(void); void DMA1_Stream4_IRQHandler(void); void ADC_IRQHandler(void); +void TIM1_UP_TIM10_IRQHandler(void); +void TIM8_UP_TIM13_IRQHandler(void); void TIM8_TRG_COM_TIM14_IRQHandler(void); void UART4_IRQHandler(void); void OTG_FS_IRQHandler(void); diff --git a/Firmware/Board/v3/Odrive.ioc b/Firmware/Board/v3/Odrive.ioc index 21d9f52f..a43aee80 100644 --- a/Firmware/Board/v3/Odrive.ioc +++ b/Firmware/Board/v3/Odrive.ioc @@ -208,7 +208,9 @@ NVIC.PendSV_IRQn=true\:15\:0\:false\:false\:false\:true\:true NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4 NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:false\:false\:true NVIC.SysTick_IRQn=true\:15\:0\:false\:false\:true\:true\:true +NVIC.TIM1_UP_TIM10_IRQn=true\:0\:0\:false\:false\:true\:false\:true NVIC.TIM8_TRG_COM_TIM14_IRQn=true\:0\:0\:false\:false\:true\:false\:false +NVIC.TIM8_UP_TIM13_IRQn=true\:0\:0\:false\:false\:true\:false\:true NVIC.TimeBase=TIM8_TRG_COM_TIM14_IRQn NVIC.TimeBaseIP=TIM14 NVIC.UART4_IRQn=true\:5\:0\:false\:false\:true\:true\:true diff --git a/Firmware/Board/v3/Src/stm32f4xx_it.c b/Firmware/Board/v3/Src/stm32f4xx_it.c index 2322c0d7..728d84a4 100644 --- a/Firmware/Board/v3/Src/stm32f4xx_it.c +++ b/Firmware/Board/v3/Src/stm32f4xx_it.c @@ -56,6 +56,7 @@ extern PCD_HandleTypeDef hpcd_USB_OTG_FS; extern ADC_HandleTypeDef hadc1; extern ADC_HandleTypeDef hadc2; extern ADC_HandleTypeDef hadc3; +extern TIM_HandleTypeDef htim1; extern TIM_HandleTypeDef htim8; extern DMA_HandleTypeDef hdma_uart4_rx; extern DMA_HandleTypeDef hdma_uart4_tx; @@ -240,6 +241,34 @@ void ADC_IRQHandler(void) /* USER CODE END ADC_IRQn 1 */ } +/** +* @brief This function handles TIM1 update interrupt and TIM10 global interrupt. +*/ +void TIM1_UP_TIM10_IRQHandler(void) +{ + /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 0 */ + + /* USER CODE END TIM1_UP_TIM10_IRQn 0 */ + HAL_TIM_IRQHandler(&htim1); + /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 1 */ + + /* USER CODE END TIM1_UP_TIM10_IRQn 1 */ +} + +/** +* @brief This function handles TIM8 update interrupt and TIM13 global interrupt. +*/ +void TIM8_UP_TIM13_IRQHandler(void) +{ + /* USER CODE BEGIN TIM8_UP_TIM13_IRQn 0 */ + + /* USER CODE END TIM8_UP_TIM13_IRQn 0 */ + HAL_TIM_IRQHandler(&htim8); + /* USER CODE BEGIN TIM8_UP_TIM13_IRQn 1 */ + + /* USER CODE END TIM8_UP_TIM13_IRQn 1 */ +} + /** * @brief This function handles TIM8 trigger and commutation interrupts and TIM14 global interrupt. */ diff --git a/Firmware/Board/v3/Src/tim.c b/Firmware/Board/v3/Src/tim.c index 5a63b6df..f1cb0c07 100644 --- a/Firmware/Board/v3/Src/tim.c +++ b/Firmware/Board/v3/Src/tim.c @@ -346,6 +346,10 @@ void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle) /* USER CODE END TIM1_MspInit 0 */ /* TIM1 clock enable */ __HAL_RCC_TIM1_CLK_ENABLE(); + + /* TIM1 interrupt Init */ + HAL_NVIC_SetPriority(TIM1_UP_TIM10_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(TIM1_UP_TIM10_IRQn); /* USER CODE BEGIN TIM1_MspInit 1 */ /* USER CODE END TIM1_MspInit 1 */ @@ -375,6 +379,8 @@ void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* tim_pwmHandle) __HAL_RCC_TIM8_CLK_ENABLE(); /* TIM8 interrupt Init */ + HAL_NVIC_SetPriority(TIM8_UP_TIM13_IRQn, 0, 0); + HAL_NVIC_EnableIRQ(TIM8_UP_TIM13_IRQn); HAL_NVIC_SetPriority(TIM8_TRG_COM_TIM14_IRQn, 0, 0); HAL_NVIC_EnableIRQ(TIM8_TRG_COM_TIM14_IRQn); /* USER CODE BEGIN TIM8_MspInit 1 */ @@ -542,6 +548,9 @@ void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle) /* USER CODE END TIM1_MspDeInit 0 */ /* Peripheral clock disable */ __HAL_RCC_TIM1_CLK_DISABLE(); + + /* TIM1 interrupt Deinit */ + HAL_NVIC_DisableIRQ(TIM1_UP_TIM10_IRQn); /* USER CODE BEGIN TIM1_MspDeInit 1 */ /* USER CODE END TIM1_MspDeInit 1 */ @@ -571,6 +580,7 @@ void HAL_TIM_PWM_MspDeInit(TIM_HandleTypeDef* tim_pwmHandle) __HAL_RCC_TIM8_CLK_DISABLE(); /* TIM8 interrupt Deinit */ + HAL_NVIC_DisableIRQ(TIM8_UP_TIM13_IRQn); HAL_NVIC_DisableIRQ(TIM8_TRG_COM_TIM14_IRQn); /* USER CODE BEGIN TIM8_MspDeInit 1 */ From 6810cd5419b8b5861c4265bc83fdd74c408abfb6 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 26 Apr 2018 14:50:27 -0700 Subject: [PATCH 152/215] move handler style to user code segment --- Firmware/Board/v3/Src/stm32f4xx_it.c | 35 +++++++++++++++------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/Firmware/Board/v3/Src/stm32f4xx_it.c b/Firmware/Board/v3/Src/stm32f4xx_it.c index 728d84a4..ebc71458 100644 --- a/Firmware/Board/v3/Src/stm32f4xx_it.c +++ b/Firmware/Board/v3/Src/stm32f4xx_it.c @@ -241,20 +241,6 @@ void ADC_IRQHandler(void) /* USER CODE END ADC_IRQn 1 */ } -/** -* @brief This function handles TIM1 update interrupt and TIM10 global interrupt. -*/ -void TIM1_UP_TIM10_IRQHandler(void) -{ - /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 0 */ - - /* USER CODE END TIM1_UP_TIM10_IRQn 0 */ - HAL_TIM_IRQHandler(&htim1); - /* USER CODE BEGIN TIM1_UP_TIM10_IRQn 1 */ - - /* USER CODE END TIM1_UP_TIM10_IRQn 1 */ -} - /** * @brief This function handles TIM8 update interrupt and TIM13 global interrupt. */ @@ -339,6 +325,25 @@ void ADC_IRQ_Dispatch(ADC_HandleTypeDef* hadc, ADC_handler_t callback) { } } +/** +* @brief This function handles TIM1 update interrupt and TIM10 global interrupt. +*/ +void TIM1_UP_TIM10_IRQHandler(void) +{ + __HAL_TIM_CLEAR_IT(&htim1, TIM_IT_UPDATE); + // TODO: Callback here +} + +/** +* @brief This function handles TIM8 update interrupt and TIM13 global interrupt. +*/ +void TIM8_UP_TIM13_IRQHandler(void) +{ + __HAL_TIM_CLEAR_IT(&htim1, TIM_IT_UPDATE); + // TODO: Callback here +} + + /** * @brief This function handles I2C1 event interrupt. */ @@ -414,7 +419,5 @@ void EXTI15_10_IRQHandler(void) HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15); } - - /* USER CODE END 1 */ /************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ From f9a5763f156d32b9ab335f3fe06bc364e704a940 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 26 Apr 2018 14:57:58 -0700 Subject: [PATCH 153/215] tim cb setup, needs impl --- Firmware/Board/v3/Inc/stm32f4xx_it.h | 2 -- Firmware/Board/v3/Odrive.ioc | 4 ++-- Firmware/Board/v3/Src/stm32f4xx_it.c | 23 +++++------------------ 3 files changed, 7 insertions(+), 22 deletions(-) diff --git a/Firmware/Board/v3/Inc/stm32f4xx_it.h b/Firmware/Board/v3/Inc/stm32f4xx_it.h index 7dbded4e..1970c2a8 100644 --- a/Firmware/Board/v3/Inc/stm32f4xx_it.h +++ b/Firmware/Board/v3/Inc/stm32f4xx_it.h @@ -57,8 +57,6 @@ void SysTick_Handler(void); void DMA1_Stream2_IRQHandler(void); void DMA1_Stream4_IRQHandler(void); void ADC_IRQHandler(void); -void TIM1_UP_TIM10_IRQHandler(void); -void TIM8_UP_TIM13_IRQHandler(void); void TIM8_TRG_COM_TIM14_IRQHandler(void); void UART4_IRQHandler(void); void OTG_FS_IRQHandler(void); diff --git a/Firmware/Board/v3/Odrive.ioc b/Firmware/Board/v3/Odrive.ioc index a43aee80..1e22adaf 100644 --- a/Firmware/Board/v3/Odrive.ioc +++ b/Firmware/Board/v3/Odrive.ioc @@ -208,9 +208,9 @@ NVIC.PendSV_IRQn=true\:15\:0\:false\:false\:false\:true\:true NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4 NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:false\:false\:true NVIC.SysTick_IRQn=true\:15\:0\:false\:false\:true\:true\:true -NVIC.TIM1_UP_TIM10_IRQn=true\:0\:0\:false\:false\:true\:false\:true +NVIC.TIM1_UP_TIM10_IRQn=true\:0\:0\:false\:false\:false\:false\:true NVIC.TIM8_TRG_COM_TIM14_IRQn=true\:0\:0\:false\:false\:true\:false\:false -NVIC.TIM8_UP_TIM13_IRQn=true\:0\:0\:false\:false\:true\:false\:true +NVIC.TIM8_UP_TIM13_IRQn=true\:0\:0\:false\:false\:false\:false\:true NVIC.TimeBase=TIM8_TRG_COM_TIM14_IRQn NVIC.TimeBaseIP=TIM14 NVIC.UART4_IRQn=true\:5\:0\:false\:false\:true\:true\:true diff --git a/Firmware/Board/v3/Src/stm32f4xx_it.c b/Firmware/Board/v3/Src/stm32f4xx_it.c index ebc71458..1ef20c8c 100644 --- a/Firmware/Board/v3/Src/stm32f4xx_it.c +++ b/Firmware/Board/v3/Src/stm32f4xx_it.c @@ -46,7 +46,9 @@ void ADC_IRQ_Dispatch(ADC_HandleTypeDef* hadc, ADC_handler_t callback); // TODO: move somewhere else void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected); void vbus_sense_adc_cb(ADC_HandleTypeDef* hadc, bool injected); +void tim_update_cb(TIM_HandleTypeDef* htim); +extern TIM_HandleTypeDef htim1; extern I2C_HandleTypeDef hi2c1; /* USER CODE END 0 */ @@ -56,7 +58,6 @@ extern PCD_HandleTypeDef hpcd_USB_OTG_FS; extern ADC_HandleTypeDef hadc1; extern ADC_HandleTypeDef hadc2; extern ADC_HandleTypeDef hadc3; -extern TIM_HandleTypeDef htim1; extern TIM_HandleTypeDef htim8; extern DMA_HandleTypeDef hdma_uart4_rx; extern DMA_HandleTypeDef hdma_uart4_tx; @@ -241,20 +242,6 @@ void ADC_IRQHandler(void) /* USER CODE END ADC_IRQn 1 */ } -/** -* @brief This function handles TIM8 update interrupt and TIM13 global interrupt. -*/ -void TIM8_UP_TIM13_IRQHandler(void) -{ - /* USER CODE BEGIN TIM8_UP_TIM13_IRQn 0 */ - - /* USER CODE END TIM8_UP_TIM13_IRQn 0 */ - HAL_TIM_IRQHandler(&htim8); - /* USER CODE BEGIN TIM8_UP_TIM13_IRQn 1 */ - - /* USER CODE END TIM8_UP_TIM13_IRQn 1 */ -} - /** * @brief This function handles TIM8 trigger and commutation interrupts and TIM14 global interrupt. */ @@ -331,7 +318,7 @@ void ADC_IRQ_Dispatch(ADC_HandleTypeDef* hadc, ADC_handler_t callback) { void TIM1_UP_TIM10_IRQHandler(void) { __HAL_TIM_CLEAR_IT(&htim1, TIM_IT_UPDATE); - // TODO: Callback here + tim_update_cb(&htim1); } /** @@ -339,8 +326,8 @@ void TIM1_UP_TIM10_IRQHandler(void) */ void TIM8_UP_TIM13_IRQHandler(void) { - __HAL_TIM_CLEAR_IT(&htim1, TIM_IT_UPDATE); - // TODO: Callback here + __HAL_TIM_CLEAR_IT(&htim8, TIM_IT_UPDATE); + tim_update_cb(&htim8); } From 8ac49bfe6e82484c81b6b9bdb8607aca3c21e100 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 26 Apr 2018 16:08:39 -0700 Subject: [PATCH 154/215] implement low level sampling --- Firmware/MotorControl/low_level.cpp | 20 +++++++++++++++++++- Firmware/MotorControl/low_level.h | 1 + Firmware/MotorControl/motor.hpp | 3 ++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 07061468..f2ae3ecc 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -35,10 +35,12 @@ float vbus_voltage = 12.0f; bool brake_resistor_armed = false; /* Private constant data -----------------------------------------------------*/ +static const GPIO_TypeDef* GPIOs_to_samp[] = { GPIOA, GPIOB, GPIOC }; +static const int num_GPIO = sizeof(GPIOs_to_samp) / sizeof(GPIOs_to_samp[0]); /* Private variables ---------------------------------------------------------*/ // Two motors, sampling port A,B,C (coherent with current meas timing) -static uint16_t GPIO_port_samples [2][3]; +static uint16_t GPIO_port_samples [2][num_GPIO]; /* CPU critical section helpers ----------------------------------------------*/ static inline uint8_t cpu_enter_critical() { @@ -419,6 +421,22 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { } } +void tim_update_cb(TIM_HandleTypeDef* htim) { + int portsamples_arr; + if (htim == &htim1) { + portsamples_arr = 0; + } else if (htim == &htim8) { + portsamples_arr = 1; + } else { + low_level_fault(Motor::ERROR_UNEXPECTED_TIMER_CALLBACK); + return; + } + + for (int i = 0; i < num_GPIO; ++i) { + GPIO_port_samples[portsamples_arr][i] = GPIOs_to_samp[i]->IDR; + } +} + // @brief Sums up the Ibus contribution of each motor and updates the // brake resistor PWM accordingly. void update_brake_current() { diff --git a/Firmware/MotorControl/low_level.h b/Firmware/MotorControl/low_level.h index 98611877..015d1b2d 100644 --- a/Firmware/MotorControl/low_level.h +++ b/Firmware/MotorControl/low_level.h @@ -34,6 +34,7 @@ void safety_critical_apply_brake_resistor_timings(uint32_t low_off, uint32_t hig extern "C" { void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected); void vbus_sense_adc_cb(ADC_HandleTypeDef* hadc, bool injected); +void tim_update_cb(TIM_HandleTypeDef* htim); } // Initalisation diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index f203cddc..b369460e 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -63,7 +63,8 @@ public: ERROR_NOT_IMPLEMENTED_MOTOR_TYPE = 0x0020, ERROR_BRAKE_CURRENT_OUT_OF_RANGE = 0x0040, ERROR_NUMERICAL = 0x0080, - ERROR_BRAKE_DEADTIME_VIOLATION = 0x0100 + ERROR_BRAKE_DEADTIME_VIOLATION = 0x0100, + ERROR_UNEXPECTED_TIMER_CALLBACK = 0x0200 }; enum TimingLog_t { From dbb16fc8e1900f3e2e9969697b934b4e01b009de Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 26 Apr 2018 16:30:42 -0700 Subject: [PATCH 155/215] turn on TIM side update IRQs --- Firmware/MotorControl/low_level.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index f2ae3ecc..e33c84f9 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -239,6 +239,10 @@ void start_adc_pwm() { __HAL_TIM_MOE_DISABLE_UNCONDITIONALLY(&htim1); __HAL_TIM_MOE_DISABLE_UNCONDITIONALLY(&htim8); + // Enable the update interrupt (used to coherently sample GPIO) + __HAL_TIM_ENABLE_IT(&htim1, TIM_IT_UPDATE); + __HAL_TIM_ENABLE_IT(&htim8, TIM_IT_UPDATE); + // Start brake resistor PWM in floating output configuration htim2.Instance->CCR3 = 0; htim2.Instance->CCR4 = TIM_APB1_PERIOD_CLOCKS + 1; From 5fc48f23dda64eeb7980c60353ff3408c15d395e Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 26 Apr 2018 17:13:10 -0700 Subject: [PATCH 156/215] enable triggering of parameter-less functions --- ArduinoI2C/ArduinoI2C.ino | 6 ++++++ ArduinoI2C/odrive.h | 28 +++++++++++++++++++++++++--- ArduinoI2C/odrive_endpoints.h | 14 +++++++++++++- tools/odrive/code_generator.py | 6 ++++++ tools/odrive_header_template.h.in | 2 +- 5 files changed, 51 insertions(+), 5 deletions(-) diff --git a/ArduinoI2C/ArduinoI2C.ino b/ArduinoI2C/ArduinoI2C.ino index 39d491a7..3a5008dd 100644 --- a/ArduinoI2C/ArduinoI2C.ino +++ b/ArduinoI2C/ArduinoI2C.ino @@ -51,6 +51,12 @@ void loop() { if (!success) Serial.println("error"); + success = odrive::trigger(odrive_num); + if (!success) + Serial.println("error"); + else + Serial.println("saved config"); + delay(500); } diff --git a/ArduinoI2C/odrive.h b/ArduinoI2C/odrive.h index fb89ccb5..88680cce 100644 --- a/ArduinoI2C/odrive.h +++ b/ArduinoI2C/odrive.h @@ -5,12 +5,16 @@ * - Implement the C function I2C_transaction to provide low level I2C access. * - Use read_property() to read properties from the ODrive. * - Use write_property() to modify properties on the ODrive. +* - Use trigger() to trigger a function (such as reboot or save_configuration) * - Use endpoint_type_t to retrieve the underlying type * of a given property. +* - Refer to PropertyId for a list of available properties. * -* To regenerate the interface definitions, flash an ODrive with the new -* firmware, then run -* ../tools/odrivetool generate-code --output odrive_endpoints.h +* To regenerate the interface definitions, flash an ODrive with +* the new firmware, connect it to your PC via USB and then run +* ../tools/odrivetool generate-code --output [path to odrive_endpoints.h] +* This step can be done with any ODrive, it doesn't have to be the +* one that you'll be controlling over I2C. */ @@ -140,4 +144,22 @@ namespace odrive { return I2C_transaction(i2c_addr + num, i2c_tx_buffer, sizeof(i2c_tx_buffer), nullptr, 0); } + /* @brief Write to an endpoint on the ODrive + * + * Usage example: + * success = odrive::trigger(0); + * + * @param num Selects the ODrive. For instance the value 4 selects + * the ODrive that has [A2, A1, A0] connected to [VCC, GND, GND]. + * @return true if the I2C transaction succeeded, false otherwise + */ + template>::value>::type> + bool trigger(uint8_t num) { + uint8_t i2c_tx_buffer[4]; + write_le(i2c_tx_buffer, IPropertyId); + write_le(i2c_tx_buffer + sizeof(i2c_tx_buffer) - 2, json_crc); + return I2C_transaction(i2c_addr + num, i2c_tx_buffer, sizeof(i2c_tx_buffer), nullptr, 0); + } + } diff --git a/ArduinoI2C/odrive_endpoints.h b/ArduinoI2C/odrive_endpoints.h index e8e4ee98..fe5e1b30 100644 --- a/ArduinoI2C/odrive_endpoints.h +++ b/ArduinoI2C/odrive_endpoints.h @@ -110,6 +110,7 @@ enum { AXIS0__CONTROLLER__CONFIG__VEL_GAIN = 93, AXIS0__CONTROLLER__CONFIG__VEL_INTEGRATOR_GAIN = 94, AXIS0__CONTROLLER__CONFIG__VEL_LIMIT = 95, + AXIS0__CONTROLLER__START_ANTICOGGING_CALIBRATION = 105, AXIS0__ENCODER__ERROR = 106, AXIS0__ENCODER__IS_READY = 107, AXIS0__ENCODER__INDEX_FOUND = 108, @@ -197,6 +198,7 @@ enum { AXIS1__CONTROLLER__CONFIG__VEL_GAIN = 190, AXIS1__CONTROLLER__CONFIG__VEL_INTEGRATOR_GAIN = 191, AXIS1__CONTROLLER__CONFIG__VEL_LIMIT = 192, + AXIS1__CONTROLLER__START_ANTICOGGING_CALIBRATION = 202, AXIS1__ENCODER__ERROR = 203, AXIS1__ENCODER__IS_READY = 204, AXIS1__ENCODER__INDEX_FOUND = 205, @@ -222,6 +224,10 @@ enum { AXIS1__SENSORLESS_ESTIMATOR__PLL_KP = 225, AXIS1__SENSORLESS_ESTIMATOR__PLL_KI = 226, TEST_PROPERTY = 227, + SAVE_CONFIGURATION = 234, + ERASE_CONFIGURATION = 235, + REBOOT = 236, + ENTER_DFU_MODE = 237, }; template @@ -322,6 +328,7 @@ template<> struct endpoint_type { typedef f template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef void type; }; template<> struct endpoint_type { typedef uint8_t type; }; template<> struct endpoint_type { typedef bool type; }; template<> struct endpoint_type { typedef bool type; }; @@ -409,6 +416,7 @@ template<> struct endpoint_type { typedef f template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef void type; }; template<> struct endpoint_type { typedef uint8_t type; }; template<> struct endpoint_type { typedef bool type; }; template<> struct endpoint_type { typedef bool type; }; @@ -434,6 +442,10 @@ template<> struct endpoint_type { typedef template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef void type; }; +template<> struct endpoint_type { typedef void type; }; +template<> struct endpoint_type { typedef void type; }; +template<> struct endpoint_type { typedef void type; }; template @@ -441,4 +453,4 @@ using endpoint_type_t = typename endpoint_type::type; } -#endif __ODRIVE_ENDPOINTS_HPP \ No newline at end of file +#endif // __ODRIVE_ENDPOINTS_HPP \ No newline at end of file diff --git a/tools/odrive/code_generator.py b/tools/odrive/code_generator.py index a3c9c096..45c01c22 100644 --- a/tools/odrive/code_generator.py +++ b/tools/odrive/code_generator.py @@ -13,6 +13,12 @@ def get_flat_endpoint_list(json, prefix): is_property = True elif item['type'] in {'bool', 'float'}: is_property = True + elif item['type'] in {'function'}: + if len(item.get('arguments', [])) == 0 and len(item.get('inputs', [])) == 0 and len(item.get('outputs', [])) == 0: + item['type'] = 'void' + is_property = True + else: + is_property = False else: is_property = False if is_property: diff --git a/tools/odrive_header_template.h.in b/tools/odrive_header_template.h.in index 05e96376..c07e0174 100644 --- a/tools/odrive_header_template.h.in +++ b/tools/odrive_header_template.h.in @@ -31,4 +31,4 @@ using endpoint_type_t = typename endpoint_type::type; } -#endif __ODRIVE_ENDPOINTS_HPP +#endif // __ODRIVE_ENDPOINTS_HPP From 7e65609e5eb884111cf1f893bf7a8e0e250f3a01 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 26 Apr 2018 17:17:57 -0700 Subject: [PATCH 157/215] inmplement hall decoder --- Firmware/MotorControl/board_config_v3.h | 19 ++++++++++++++ Firmware/MotorControl/encoder.hpp | 2 +- Firmware/MotorControl/low_level.cpp | 34 +++++++++++++++++++++++-- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/Firmware/MotorControl/board_config_v3.h b/Firmware/MotorControl/board_config_v3.h index 46b7d5ae..45e46a7d 100644 --- a/Firmware/MotorControl/board_config_v3.h +++ b/Firmware/MotorControl/board_config_v3.h @@ -32,6 +32,12 @@ typedef struct { TIM_HandleTypeDef* timer; GPIO_TypeDef* index_port; uint16_t index_pin; + GPIO_TypeDef* hallA_port; + uint16_t hallA_pin; + GPIO_TypeDef* hallB_port; + uint16_t hallB_pin; + GPIO_TypeDef* hallC_port; + uint16_t hallC_pin; } EncoderHardwareConfig_t; typedef struct { TIM_HandleTypeDef* timer; @@ -56,6 +62,7 @@ typedef struct { extern const BoardHardwareConfig_t hw_configs[2]; +//TODO stick this in a C file #ifdef __MAIN_CPP__ const BoardHardwareConfig_t hw_configs[2] = { { .axis_config = { @@ -69,6 +76,12 @@ const BoardHardwareConfig_t hw_configs[2] = { { .timer = &htim3, .index_port = M0_ENC_Z_GPIO_Port, .index_pin = M0_ENC_Z_Pin, + .hallA_port = M0_ENC_A_GPIO_Port, + .hallA_pin = M0_ENC_A_Pin, + .hallB_port = M0_ENC_B_GPIO_Port, + .hallB_pin = M0_ENC_B_Pin, + .hallC_port = M0_ENC_Z_GPIO_Port, + .hallC_pin = M0_ENC_Z_Pin, }, .motor_config = { .timer = &htim1, @@ -97,6 +110,12 @@ const BoardHardwareConfig_t hw_configs[2] = { { .timer = &htim4, .index_port = M1_ENC_Z_GPIO_Port, .index_pin = M1_ENC_Z_Pin, + .hallA_port = M1_ENC_A_GPIO_Port, + .hallA_pin = M1_ENC_A_Pin, + .hallB_port = M1_ENC_B_GPIO_Port, + .hallB_pin = M1_ENC_B_Pin, + .hallC_port = M1_ENC_Z_GPIO_Port, + .hallC_pin = M1_ENC_Z_Pin, }, .motor_config = { .timer = &htim8, diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 74836fad..59841a8f 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -62,7 +62,7 @@ public: float pll_ki_ = 0.0f; // [(rad/s^2) / rad] // Updated by low_level pwm_adc_cb - bool hallA_ = 0, hallB_ = 0, hallC_ = 0; + uint8_t hall_state = 0x0; // bit[0] = HallA, .., bit[2] = HallC // Communication protocol definitions auto make_protocol_definitions() { diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index e33c84f9..1858d457 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -36,7 +36,7 @@ float vbus_voltage = 12.0f; bool brake_resistor_armed = false; /* Private constant data -----------------------------------------------------*/ static const GPIO_TypeDef* GPIOs_to_samp[] = { GPIOA, GPIOB, GPIOC }; -static const int num_GPIO = sizeof(GPIOs_to_samp) / sizeof(GPIOs_to_samp[0]); +static const int num_GPIO = sizeof(GPIOs_to_samp) / sizeof(GPIOs_to_samp[0]); /* Private variables ---------------------------------------------------------*/ // Two motors, sampling port A,B,C (coherent with current meas timing) @@ -325,7 +325,6 @@ void sync_timers(TIM_HandleTypeDef* htim_a, TIM_HandleTypeDef* htim_b, // IRQ Callbacks //-------------------------------- - void vbus_sense_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { static const float voltage_scale = 3.3f * VBUS_S_DIVIDER_RATIO / (float)(1 << 12); // Only one conversion in sequence, so only rank1 @@ -338,6 +337,34 @@ void vbus_sense_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { } } +static void decode_hall_samples(Encoder& enc, uint16_t GPIO_samples[num_GPIO]) { + GPIO_TypeDef* hall_ports[] = { + enc.hw_config_.hallA_port, + enc.hw_config_.hallB_port, + enc.hw_config_.hallC_port + }; + uint16_t hall_pins[] = { + enc.hw_config_.hallA_pin, + enc.hw_config_.hallB_pin, + enc.hw_config_.hallC_pin + }; + + uint8_t hall_state = 0x0; + for (int i = 0; i < 3; ++i) { + int port_idx = 0; + for (;;) { + auto port = GPIOs_to_samp[port_idx]; + if (port == hall_ports[i]) + break; + } + + hall_state <<= 1; + hall_state |= (GPIO_samples[port_idx] & hall_pins[i]) ? 1 : 0; + } + + enc.hall_state = hall_state; +} + // This is the callback from the ADC that we expect after the PWM has triggered an ADC conversion. // TODO: Document how the phasing is done, link to timing diagram void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { @@ -355,6 +382,7 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { // If the corresponding timer is counting up, we just sampled in SVM vector 0, i.e. real current // If we are counting down, we just sampled in SVM vector 7, with zero current Axis& axis = injected ? *axes[0] : *axes[1]; + int axis_num = injected ? 0 : 1; Axis& other_axis = injected ? *axes[1] : *axes[0]; bool counting_down = axis.motor_.hw_config_.timer->Instance->CR1 & TIM_CR1_DIR; @@ -413,6 +441,8 @@ void pwm_trig_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { } else { axis.motor_.current_meas_.phC = current - axis.motor_.DC_calib_.phC; } + // Prepare hall readings + decode_hall_samples(axis.encoder_, GPIO_port_samples[axis_num]); // Trigger axis thread axis.signal_current_meas(); } else { From b5a7be71dfb930fe3d725a2fd5459c3cbae09188 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 26 Apr 2018 21:31:46 -0700 Subject: [PATCH 158/215] decode hall --- Firmware/MotorControl/encoder.cpp | 40 ++++++++++++++++++++++++----- Firmware/MotorControl/encoder.hpp | 12 ++++++++- Firmware/MotorControl/low_level.cpp | 11 ++++---- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 168bc0e0..4c00ca6c 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -26,6 +26,32 @@ void Encoder::setup() { enc_index_cb_wrapper, this); } +int16_t Encoder::get_low_level_count() { + switch (mode_) { + case MODE_INCREMENTAL: { + return (int16_t)hw_config_.timer->Instance->CNT; + } break; + case MODE_HALL: { + switch (hall_state_) { + case 0b001: return 0; + case 0b011: return 1; + case 0b010: return 2; + case 0b110: return 3; + case 0b100: return 4; + case 0b101: return 5; + default: { + error_ |= ERROR_ILLEGAL_HALL_STATE; + return 0; + } + } + } break; + default: { + error_ |= ERROR_UNSUPPORTED_ENCODER_MODE; + return 0; + } + } +} + //-------------------- // Hardware Dependent //-------------------- @@ -142,7 +168,7 @@ bool Encoder::run_offset_calibration() { if (axis_->error_ != Axis::ERROR_NO_ERROR) return false; - int32_t init_enc_val = (int16_t)hw_config_.timer->Instance->CNT; + int32_t init_enc_val = get_low_level_count(); int64_t encvaluesum = 0; // scan forward @@ -157,7 +183,7 @@ bool Encoder::run_offset_calibration() { return false; // error set inside enqueue_voltage_timings axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB); - encvaluesum += (int16_t)hw_config_.timer->Instance->CNT; + encvaluesum += get_low_level_count(); return ++i < num_steps; }); @@ -167,17 +193,17 @@ bool Encoder::run_offset_calibration() { //TODO avoid recomputing elec_rad_per_enc every time float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / (float)(config_.cpr)); float expected_encoder_delta = scan_distance / elec_rad_per_enc; - float actual_encoder_delta_abs = fabsf((int16_t)hw_config_.timer->Instance->CNT-init_enc_val); + float actual_encoder_delta_abs = fabsf(get_low_level_count()-init_enc_val); if(fabsf(actual_encoder_delta_abs - expected_encoder_delta)/expected_encoder_delta > config_.calib_range) { error_ |= ERROR_CPR_OUT_OF_RANGE; return false; } // check direction - if ((int16_t)hw_config_.timer->Instance->CNT > init_enc_val + 8) { + if (get_low_level_count() > init_enc_val + 8) { // motor same dir as encoder axis_->motor_.config_.direction = 1; - } else if ((int16_t)hw_config_.timer->Instance->CNT < init_enc_val - 8) { + } else if (get_low_level_count() < init_enc_val - 8) { // motor opposite dir as encoder axis_->motor_.config_.direction = -1; } else { @@ -196,7 +222,7 @@ bool Encoder::run_offset_calibration() { return false; // error set inside enqueue_voltage_timings axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB); - encvaluesum += (int16_t)hw_config_.timer->Instance->CNT; + encvaluesum += get_low_level_count(); return ++i < num_steps; }); @@ -217,7 +243,7 @@ bool Encoder::update(float* pos_estimate, float* vel_estimate, float* phase_outp } // update internal encoder state - int16_t delta_enc_16 = (int16_t)hw_config_.timer->Instance->CNT - (int16_t)shadow_count_; + int16_t delta_enc_16 = get_low_level_count() - (int16_t)shadow_count_; int32_t delta_enc = (int32_t)delta_enc_16; //sign extend shadow_count_ += delta_enc; count_in_cpr_ += delta_enc; diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 59841a8f..7a67ffb9 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -26,6 +26,13 @@ public: ERROR_NUMERICAL = 0x01, ERROR_CPR_OUT_OF_RANGE = 0x02, ERROR_RESPONSE = 0x04, + ERROR_UNSUPPORTED_ENCODER_MODE = 0x08, + ERROR_ILLEGAL_HALL_STATE = 0x10, + }; + + enum Mode_t { + MODE_INCREMENTAL, + MODE_HALL }; Encoder(const EncoderHardwareConfig_t& hw_config, @@ -35,6 +42,7 @@ public: void enc_index_cb(); + int16_t get_low_level_count(); void set_linear_count(int32_t count); void set_circular_count(int32_t count); bool calib_enc_offset(float voltage_magnitude); @@ -49,6 +57,7 @@ public: Axis* axis_ = nullptr; // set by Axis constructor Error_t error_ = ERROR_NONE; + Mode_t mode_ = MODE_INCREMENTAL; bool index_found_ = false; bool is_ready_ = false; int32_t shadow_count_ = 0; @@ -62,7 +71,7 @@ public: float pll_ki_ = 0.0f; // [(rad/s^2) / rad] // Updated by low_level pwm_adc_cb - uint8_t hall_state = 0x0; // bit[0] = HallA, .., bit[2] = HallC + uint8_t hall_state_ = 0x0; // bit[0] = HallA, .., bit[2] = HallC // Communication protocol definitions auto make_protocol_definitions() { @@ -76,6 +85,7 @@ public: make_protocol_property("phase", &phase_), make_protocol_property("pos_estimate", &pos_estimate_), make_protocol_property("pos_cpr", &pos_cpr_), + make_protocol_property("hall_state", &hall_state_), make_protocol_property("pll_vel", &pll_vel_), make_protocol_property("pll_kp", &pll_kp_), make_protocol_property("pll_ki", &pll_ki_), diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 1858d457..8477ee32 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -339,14 +339,14 @@ void vbus_sense_adc_cb(ADC_HandleTypeDef* hadc, bool injected) { static void decode_hall_samples(Encoder& enc, uint16_t GPIO_samples[num_GPIO]) { GPIO_TypeDef* hall_ports[] = { - enc.hw_config_.hallA_port, + enc.hw_config_.hallC_port, enc.hw_config_.hallB_port, - enc.hw_config_.hallC_port + enc.hw_config_.hallA_port, }; uint16_t hall_pins[] = { - enc.hw_config_.hallA_pin, + enc.hw_config_.hallC_pin, enc.hw_config_.hallB_pin, - enc.hw_config_.hallC_pin + enc.hw_config_.hallA_pin, }; uint8_t hall_state = 0x0; @@ -356,13 +356,14 @@ static void decode_hall_samples(Encoder& enc, uint16_t GPIO_samples[num_GPIO]) { auto port = GPIOs_to_samp[port_idx]; if (port == hall_ports[i]) break; + ++port_idx; } hall_state <<= 1; hall_state |= (GPIO_samples[port_idx] & hall_pins[i]) ? 1 : 0; } - enc.hall_state = hall_state; + enc.hall_state_ = hall_state; } // This is the callback from the ADC that we expect after the PWM has triggered an ADC conversion. From 52b179d49867735863d0df47be62326a5f216743 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 27 Apr 2018 00:26:43 -0700 Subject: [PATCH 159/215] working hall, suspect commutation angle noise --- Firmware/MotorControl/encoder.cpp | 98 ++++++++++++++++++------------- Firmware/MotorControl/encoder.hpp | 36 ++++++------ Firmware/MotorControl/main.cpp | 6 +- 3 files changed, 79 insertions(+), 61 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 4c00ca6c..020046c3 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -3,13 +3,13 @@ Encoder::Encoder(const EncoderHardwareConfig_t& hw_config, - EncoderConfig_t& config) : + Config_t& config) : hw_config_(hw_config), config_(config) { // Calculate encoder pll gains // This calculation is currently identical to the PLL in SensorlessEstimator - float pll_bandwidth = 1000.0f; // [rad/s] + float pll_bandwidth = 100.0f; // [rad/s] pll_kp_ = 2.0f * pll_bandwidth; // Critically damped @@ -26,32 +26,6 @@ void Encoder::setup() { enc_index_cb_wrapper, this); } -int16_t Encoder::get_low_level_count() { - switch (mode_) { - case MODE_INCREMENTAL: { - return (int16_t)hw_config_.timer->Instance->CNT; - } break; - case MODE_HALL: { - switch (hall_state_) { - case 0b001: return 0; - case 0b011: return 1; - case 0b010: return 2; - case 0b110: return 3; - case 0b100: return 4; - case 0b101: return 5; - default: { - error_ |= ERROR_ILLEGAL_HALL_STATE; - return 0; - } - } - } break; - default: { - error_ |= ERROR_UNSUPPORTED_ENCODER_MODE; - return 0; - } - } -} - //-------------------- // Hardware Dependent //-------------------- @@ -120,6 +94,8 @@ bool Encoder::run_index_search() { index_found_ = false; float phase = 0.0f; axis_->run_control_loop([&](){ + update(nullptr, nullptr, nullptr); + phase = wrap_pm_pi(phase + omega * current_meas_period); float v_alpha = voltage_magnitude * arm_cos_f32(phase); @@ -160,6 +136,8 @@ bool Encoder::run_offset_calibration() { // go to motor zero phase for start_lock_duration to get ready to scan int i = 0; axis_->run_control_loop([&](){ + update(nullptr, nullptr, nullptr); + if (!axis_->motor_.enqueue_voltage_timings(voltage_magnitude, 0.0f)) return false; // error set inside enqueue_voltage_timings axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB); @@ -168,13 +146,13 @@ bool Encoder::run_offset_calibration() { if (axis_->error_ != Axis::ERROR_NO_ERROR) return false; - int32_t init_enc_val = get_low_level_count(); + int32_t init_enc_val = shadow_count_; int64_t encvaluesum = 0; // scan forward i = 0; axis_->run_control_loop([&](){ - axis_->encoder_.update(nullptr, nullptr, nullptr); + update(nullptr, nullptr, nullptr); float phase = wrap_pm_pi(scan_distance * (float)i / (float)num_steps - scan_distance / 2.0f); float v_alpha = voltage_magnitude * arm_cos_f32(phase); @@ -183,7 +161,7 @@ bool Encoder::run_offset_calibration() { return false; // error set inside enqueue_voltage_timings axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB); - encvaluesum += get_low_level_count(); + encvaluesum += shadow_count_; return ++i < num_steps; }); @@ -193,17 +171,17 @@ bool Encoder::run_offset_calibration() { //TODO avoid recomputing elec_rad_per_enc every time float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / (float)(config_.cpr)); float expected_encoder_delta = scan_distance / elec_rad_per_enc; - float actual_encoder_delta_abs = fabsf(get_low_level_count()-init_enc_val); + float actual_encoder_delta_abs = fabsf(shadow_count_-init_enc_val); if(fabsf(actual_encoder_delta_abs - expected_encoder_delta)/expected_encoder_delta > config_.calib_range) { error_ |= ERROR_CPR_OUT_OF_RANGE; return false; } // check direction - if (get_low_level_count() > init_enc_val + 8) { + if (shadow_count_ > init_enc_val + 8) { // motor same dir as encoder axis_->motor_.config_.direction = 1; - } else if (get_low_level_count() < init_enc_val - 8) { + } else if (shadow_count_ < init_enc_val - 8) { // motor opposite dir as encoder axis_->motor_.config_.direction = -1; } else { @@ -215,6 +193,8 @@ bool Encoder::run_offset_calibration() { // scan backwards i = 0; axis_->run_control_loop([&](){ + update(nullptr, nullptr, nullptr); + float phase = wrap_pm_pi(-scan_distance * (float)i / (float)num_steps + scan_distance / 2.0f); float v_alpha = voltage_magnitude * arm_cos_f32(phase); float v_beta = voltage_magnitude * arm_sin_f32(phase); @@ -222,7 +202,7 @@ bool Encoder::run_offset_calibration() { return false; // error set inside enqueue_voltage_timings axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB); - encvaluesum += get_low_level_count(); + encvaluesum += shadow_count_; return ++i < num_steps; }); @@ -235,6 +215,18 @@ bool Encoder::run_offset_calibration() { return true; } +static bool decode_hall(uint8_t hall_state, int32_t* hall_cnt) { + switch (hall_state) { + case 0b001: *hall_cnt = 0; return true; + case 0b011: *hall_cnt = 1; return true; + case 0b010: *hall_cnt = 2; return true; + case 0b110: *hall_cnt = 3; return true; + case 0b100: *hall_cnt = 4; return true; + case 0b101: *hall_cnt = 5; return true; + default: return false; + } +} + bool Encoder::update(float* pos_estimate, float* vel_estimate, float* phase_output) { // Check that we don't get problems with discrete time approximation if (!(current_meas_period * pll_kp_ < 1.0f)) { @@ -242,9 +234,35 @@ bool Encoder::update(float* pos_estimate, float* vel_estimate, float* phase_outp return false; } - // update internal encoder state - int16_t delta_enc_16 = get_low_level_count() - (int16_t)shadow_count_; - int32_t delta_enc = (int32_t)delta_enc_16; //sign extend + // update internal encoder state. + int32_t delta_enc = 0; + switch (config_.mode) { + case MODE_INCREMENTAL: { + //TODO: use count_in_cpr_ instead as shadow_count_ can overflow + //or use 64 bit + int16_t delta_enc_16 = (int16_t)hw_config_.timer->Instance->CNT - (int16_t)shadow_count_; + delta_enc = (int32_t)delta_enc_16; //sign extend + } break; + + case MODE_HALL: { + int32_t hall_cnt; + if (decode_hall(hall_state_, &hall_cnt)) { + delta_enc = hall_cnt - count_in_cpr_; + delta_enc = mod(delta_enc, 6); + if (delta_enc > 3) + delta_enc -= 6; + } else { + error_ |= ERROR_ILLEGAL_HALL_STATE; + return false; + } + } break; + + default: { + error_ |= ERROR_UNSUPPORTED_ENCODER_MODE; + return 0; + } break; + } + shadow_count_ += delta_enc; count_in_cpr_ += delta_enc; count_in_cpr_ = mod(count_in_cpr_, config_.cpr); @@ -261,14 +279,14 @@ bool Encoder::update(float* pos_estimate, float* vel_estimate, float* phase_outp // run pll (for now pll is in units of encoder counts) // Predict current pos pos_estimate_ += current_meas_period * pll_vel_; - pos_cpr_ += current_meas_period * pll_vel_; + pos_cpr_ += current_meas_period * pll_vel_; // discrete phase detector float delta_pos = (float)(shadow_count_ - (int32_t)floorf(pos_estimate_)); float delta_pos_cpr = (float)(count_in_cpr_ - (int32_t)floorf(pos_cpr_)); delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * (float)(config_.cpr)); // pll feedback pos_estimate_ += current_meas_period * pll_kp_ * delta_pos; - pos_cpr_ += current_meas_period * pll_kp_ * delta_pos_cpr; + pos_cpr_ += current_meas_period * pll_kp_ * delta_pos_cpr; pos_cpr_ = fmodf_pos(pos_cpr_, (float)(config_.cpr)); pll_vel_ += current_meas_period * pll_ki_ * delta_pos_cpr; if (fabsf(pll_vel_) < 0.5f * current_meas_period * pll_ki_) diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 7a67ffb9..c7524302 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -5,20 +5,6 @@ #error "This file should not be included directly. Include odrive_main.h instead." #endif -struct EncoderConfig_t { - bool use_index = false; - bool pre_calibrated = false; // If true, this means the offset stored in - // configuration is valid and does not need - // be determined by run_offset_calibration. - // In this case the encoder will enter ready - // state as soon as the index is found. - float idx_search_speed = 10.0f; // [rad/s electrical] - int32_t cpr = (2048 * 4); // Default resolution of CUI-AMT102 encoder, - int32_t offset = 0; // If pre_calibrated is true, this is copied into encoder.offset_ once - // index search succeeds - float calib_range = 0.02f; -}; - class Encoder { public: enum Error_t { @@ -35,14 +21,28 @@ public: MODE_HALL }; + struct Config_t { + Encoder::Mode_t mode = Encoder::MODE_INCREMENTAL; + bool use_index = false; + bool pre_calibrated = false; // If true, this means the offset stored in + // configuration is valid and does not need + // be determined by run_offset_calibration. + // In this case the encoder will enter ready + // state as soon as the index is found. + float idx_search_speed = 10.0f; // [rad/s electrical] + int32_t cpr = (2048 * 4); // Default resolution of CUI-AMT102 encoder, + int32_t offset = 0; // If pre_calibrated is true, this is copied into encoder.offset_ once + // index search succeeds + float calib_range = 0.02f; + }; + Encoder(const EncoderHardwareConfig_t& hw_config, - EncoderConfig_t& config); + Config_t& config); void setup(); void enc_index_cb(); - int16_t get_low_level_count(); void set_linear_count(int32_t count); void set_circular_count(int32_t count); bool calib_enc_offset(float voltage_magnitude); @@ -53,11 +53,10 @@ public: bool update(float* pos_estimate, float* vel_estimate, float* phase); const EncoderHardwareConfig_t& hw_config_; - EncoderConfig_t& config_; + Config_t& config_; Axis* axis_ = nullptr; // set by Axis constructor Error_t error_ = ERROR_NONE; - Mode_t mode_ = MODE_INCREMENTAL; bool index_found_ = false; bool is_ready_ = false; int32_t shadow_count_ = 0; @@ -90,6 +89,7 @@ public: make_protocol_property("pll_kp", &pll_kp_), make_protocol_property("pll_ki", &pll_ki_), make_protocol_object("config", + make_protocol_property("mode", &config_.mode), make_protocol_property("use_index", &config_.use_index), make_protocol_property("pre_calibrated", &config_.pre_calibrated), make_protocol_property("idx_search_speed", &config_.idx_search_speed), diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 2d744d5b..1e18a744 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -9,7 +9,7 @@ #include BoardConfig_t board_config; -EncoderConfig_t encoder_configs[AXIS_COUNT]; +Encoder::Config_t encoder_configs[AXIS_COUNT]; ControllerConfig_t controller_configs[AXIS_COUNT]; MotorConfig_t motor_configs[AXIS_COUNT]; AxisConfig_t axis_configs[AXIS_COUNT]; @@ -21,7 +21,7 @@ Axis *axes[AXIS_COUNT]; typedef Config< BoardConfig_t, - EncoderConfig_t[AXIS_COUNT], + Encoder::Config_t[AXIS_COUNT], ControllerConfig_t[AXIS_COUNT], MotorConfig_t[AXIS_COUNT], AxisConfig_t[AXIS_COUNT]> ConfigFormat; @@ -49,7 +49,7 @@ void load_configuration(void) { //If loading failed, restore defaults board_config = BoardConfig_t(); for (size_t i = 0; i < AXIS_COUNT; ++i) { - encoder_configs[i] = EncoderConfig_t(); + encoder_configs[i] = Encoder::Config_t(); controller_configs[i] = ControllerConfig_t(); motor_configs[i] = MotorConfig_t(); axis_configs[i] = AxisConfig_t(); From f480059905ad29b51f1dca3e411ebc7992fee7dc Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 27 Apr 2018 01:27:42 -0700 Subject: [PATCH 160/215] add sub-count phase alignment offset --- Firmware/MotorControl/encoder.cpp | 4 +++- Firmware/MotorControl/encoder.hpp | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 020046c3..bb0b2603 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -210,6 +210,8 @@ bool Encoder::run_offset_calibration() { return false; offset_ = encvaluesum / (num_steps * 2); + int32_t residual = encvaluesum - ((int64_t)offset_ * (int64_t)(num_steps * 2)); + config_.offset_float = (float)residual / (float)(num_steps * 2); is_ready_ = true; config_.use_index = old_use_index; return true; @@ -271,7 +273,7 @@ bool Encoder::update(float* pos_estimate, float* vel_estimate, float* phase_outp int corrected_enc = count_in_cpr_ - offset_; //TODO avoid recomputing elec_rad_per_enc every time float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / (float)(config_.cpr)); - float ph = elec_rad_per_enc * (float)corrected_enc; + float ph = elec_rad_per_enc * ((float)corrected_enc - config_.offset_float); // ph = fmodf(ph, 2*M_PI); phase_ = wrap_pm_pi(ph); diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index c7524302..8b5fc8c2 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -33,6 +33,7 @@ public: int32_t cpr = (2048 * 4); // Default resolution of CUI-AMT102 encoder, int32_t offset = 0; // If pre_calibrated is true, this is copied into encoder.offset_ once // index search succeeds + float offset_float = 0.0f; // Sub-count phase alignment offset float calib_range = 0.02f; }; @@ -95,6 +96,7 @@ public: make_protocol_property("idx_search_speed", &config_.idx_search_speed), make_protocol_property("cpr", &config_.cpr), make_protocol_property("offset", &config_.offset), + make_protocol_property("offset_float", &config_.offset_float), make_protocol_property("calib_range", &config_.calib_range) ) ); From 512ba5e60394919a22666f7c5edc83755d12cd70 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 27 Apr 2018 02:12:35 -0700 Subject: [PATCH 161/215] implement interpolation --- Firmware/MotorControl/encoder.cpp | 25 ++++++++++++++++++++----- Firmware/MotorControl/encoder.hpp | 2 ++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index bb0b2603..222fcd6a 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -269,16 +269,31 @@ bool Encoder::update(float* pos_estimate, float* vel_estimate, float* phase_outp count_in_cpr_ += delta_enc; count_in_cpr_ = mod(count_in_cpr_, config_.cpr); - // compute electrical phase - int corrected_enc = count_in_cpr_ - offset_; + + //// run encoder count interpolation + int32_t corrected_enc = count_in_cpr_ - offset_; + // reset interpolation if encoder edge comes + if (delta_enc > 0) { + interpolation_ = 0.0f; + } else if (delta_enc < 0) { + interpolation_ = 1.0f; + } else { + // Interpolate (predict) between encoder counts using pll_vel, + interpolation_ += current_meas_period * pll_vel_; + // don't allow interpolation indicated position outside of [enc, enc+1) + if (interpolation_ > 1.0f) interpolation_ = 1.0f; + if (interpolation_ < 0.0f) interpolation_ = 0.0f; + } + float interpolated_enc = corrected_enc + interpolation_; + + //// compute electrical phase //TODO avoid recomputing elec_rad_per_enc every time float elec_rad_per_enc = axis_->motor_.config_.pole_pairs * 2 * M_PI * (1.0f / (float)(config_.cpr)); - float ph = elec_rad_per_enc * ((float)corrected_enc - config_.offset_float); + float ph = elec_rad_per_enc * (interpolated_enc - config_.offset_float); // ph = fmodf(ph, 2*M_PI); phase_ = wrap_pm_pi(ph); - - // run pll (for now pll is in units of encoder counts) + //// run pll (for now pll is in units of encoder counts) // Predict current pos pos_estimate_ += current_meas_period * pll_vel_; pos_cpr_ += current_meas_period * pll_vel_; diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 8b5fc8c2..4dc67013 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -63,6 +63,7 @@ public: int32_t shadow_count_ = 0; int32_t count_in_cpr_ = 0; int32_t offset_ = 0; + float interpolation_ = 0.0f; float phase_ = 0.0f; // [rad] float pos_estimate_ = 0.0f; // [rad] float pos_cpr_ = 0.0f; // [rad] @@ -82,6 +83,7 @@ public: make_protocol_property("shadow_count", &shadow_count_), make_protocol_property("count_in_cpr", &count_in_cpr_), make_protocol_property("offset", &offset_), + make_protocol_property("interpolation", &interpolation_), make_protocol_property("phase", &phase_), make_protocol_property("pos_estimate", &pos_estimate_), make_protocol_property("pos_cpr", &pos_cpr_), From d827d7f1d1e1a182cbcc874f592746e6f43c1771 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 27 Apr 2018 12:08:23 -0700 Subject: [PATCH 162/215] set encoder to ready state on startup for hall effect mode --- Firmware/MotorControl/encoder.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 222fcd6a..07a6b3a1 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -14,6 +14,11 @@ Encoder::Encoder(const EncoderHardwareConfig_t& hw_config, // Critically damped pll_ki_ = 0.25f * (pll_kp_ * pll_kp_); + + if (config.pre_calibrated && (config.mode == Encoder::MODE_HALL)) { + offset_ = config.offset; + is_ready_ = true; + } } static void enc_index_cb_wrapper(void* ctx) { From f78a2b7d0a6dd18c267b9b44765397ef2602ab4a Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 27 Apr 2018 15:04:26 -0700 Subject: [PATCH 163/215] update arduino demo --- ArduinoI2C/ArduinoI2C.ino | 156 +++++++++- ArduinoI2C/odrive.h | 54 +++- ArduinoI2C/odrive_endpoints.h | 559 ++++++++++++++++++---------------- 3 files changed, 472 insertions(+), 297 deletions(-) diff --git a/ArduinoI2C/ArduinoI2C.ino b/ArduinoI2C/ArduinoI2C.ino index 3a5008dd..e5b852ea 100644 --- a/ArduinoI2C/ArduinoI2C.ino +++ b/ArduinoI2C/ArduinoI2C.ino @@ -28,35 +28,159 @@ bool I2C_transaction(uint8_t slave_addr, const uint8_t * tx_buffer, size_t tx_le } +int set_and_save_configuration(uint8_t odrive_num, uint8_t axis_num) { + bool success; + success = odrive::clear_errors(odrive_num, axis_num); + if (!success) + return __LINE__; + + // select hall effect mode + success = odrive::write_axis_property(odrive_num, axis_num, 1); + if (!success) + return __LINE__; + + // configure encoder counts per revolution (6 hall effect states * 12 pole pairs) + success = odrive::write_axis_property(odrive_num, axis_num, 72); + if (!success) + return __LINE__; + + // disable velocity integrator + success = odrive::write_axis_property(odrive_num, axis_num, 0); + if (!success) + return __LINE__; + + // select velocity control + success = odrive::write_axis_property(odrive_num, axis_num, 2); + if (!success) + return __LINE__; + + // set velocity controller P-gain + success = odrive::write_axis_property(odrive_num, axis_num, 0.005f); + if (!success) + return __LINE__; + + // request state: motor calibration + success = odrive::write_axis_property(odrive_num, axis_num, 4); + if (!success) + return __LINE__; + + delay(6000); + + // check if the axis is in idle and no errors occurred + if (!odrive::check_axis_state(odrive_num, axis_num, 1)) + return __LINE__; + + // ensure that the motor calibration is considered valid after power cycle + success = odrive::write_axis_property(odrive_num, axis_num, true); + if (!success) + return __LINE__; + + // request state: encoder calibration + success = odrive::write_axis_property(odrive_num, axis_num, 7); + if (!success) + return __LINE__; + + delay(12000); + + // check if the axis is in idle and no errors occurred + if (!odrive::check_axis_state(odrive_num, axis_num, 1)) + return __LINE__; + + // ensure that the encoder calibration is considered valid after power cycle + success = odrive::write_axis_property(odrive_num, axis_num, true); + if (!success) + return __LINE__; + + // store the configuration to NVM + // Caution: this operation is usually instantaneous but after every couple of hundred calls it will + // take around 1 second (because a flash page needs to be erased). + success = odrive::trigger(odrive_num); + if (!success) + return __LINE__; + return 0; +} + + +byte odrive_num = 7; +byte axis_num = 0; +bool do_setup = true; + void setup() { Wire.begin(); // join i2c bus (address optional for master) Serial.begin(9600); + + if (do_setup) { + Serial.println("Starting ODrive setup..."); + int error_line = set_and_save_configuration(odrive_num, axis_num); + if (error_line != 0) { + Serial.print("ODrive setup failed at line "); + Serial.print(error_line); + Serial.println(); + return; + } + Serial.println("ODrive setup succeeded!"); + do_setup = false; + } } -byte odrive_num = 7; -byte x; void loop() { bool success; + delay(500); - float val; - success = odrive::read_property(odrive_num, &val); - if (success) { - Serial.println(val, HEX); - } else { - Serial.println("error"); + success = odrive::check_axis_state(odrive_num, axis_num, 8); + if (!success) { + Serial.println("not in closed loop control - entering closed loop control"); + + // clear previous error state + success = odrive::clear_errors(odrive_num, axis_num); + if (!success) { + Serial.println("could not enter closed loop control"); + return; + } + + // request velocity 0 + success = odrive::write_axis_property(odrive_num, axis_num, 0); + if (!success) { + Serial.println("could not enter closed loop control"); + return; + } + + // request state: closed loop control + success = odrive::write_axis_property(odrive_num, axis_num, 8); + if (!success) { + Serial.println("could not enter closed loop control"); + return; + } + + success = odrive::check_axis_state(odrive_num, axis_num, 8); + if (!success) { + Serial.println("could not enter closed loop control"); + return; + } } - success = odrive::write_property(odrive_num, x++); - if (!success) + success = odrive::write_axis_property(odrive_num, axis_num, 72 * 5); + if (!success) { Serial.println("error"); - - success = odrive::trigger(odrive_num); - if (!success) - Serial.println("error"); - else - Serial.println("saved config"); + return; + } delay(500); + + success = odrive::write_axis_property(odrive_num, axis_num, -72 * 5); + if (!success) { + Serial.println("error"); + return; + } + + // print Vbus to show liveness + float vbus; + success = odrive::read_property(odrive_num, &vbus); + if (!success) { + Serial.println("error"); + return; + } + Serial.println(vbus); } diff --git a/ArduinoI2C/odrive.h b/ArduinoI2C/odrive.h index 88680cce..9458af55 100644 --- a/ArduinoI2C/odrive.h +++ b/ArduinoI2C/odrive.h @@ -101,7 +101,8 @@ namespace odrive { write_le(buffer, *reinterpret_cast(&value)); } - /* @brief Read from an endpoint on the ODrive + /* @brief Read from an endpoint on the ODrive. + * To read from an axis specific endpoint use read_axis_property() instead. * * Usage example: * float val; @@ -112,9 +113,9 @@ namespace odrive { * @return true if the I2C transaction succeeded, false otherwise */ template - bool read_property(uint8_t num, endpoint_type_t* value) { + bool read_property(uint8_t num, endpoint_type_t* value, uint16_t address = IPropertyId) { uint8_t i2c_tx_buffer[4]; - write_le(i2c_tx_buffer, IPropertyId); + write_le(i2c_tx_buffer, address); write_le(i2c_tx_buffer + sizeof(i2c_tx_buffer) - 2, json_crc); uint8_t i2c_rx_buffer[byte_width>::value]; if (!I2C_transaction(i2c_addr + num, @@ -126,25 +127,26 @@ namespace odrive { return true; } - /* @brief Write to an endpoint on the ODrive + /* @brief Write to an endpoint on the ODrive. + * To write to an axis specific endpoint use write_axis_property() instead. * * Usage example: - * success = odrive::write_property(0, 10000); + * success = odrive::write_property(0, 42); * * @param num Selects the ODrive. For instance the value 4 selects * the ODrive that has [A2, A1, A0] connected to [VCC, GND, GND]. * @return true if the I2C transaction succeeded, false otherwise */ template - bool write_property(uint8_t num, endpoint_type_t value) { + bool write_property(uint8_t num, endpoint_type_t value, uint16_t address = IPropertyId) { uint8_t i2c_tx_buffer[4 + byte_width>::value]; - write_le(i2c_tx_buffer, IPropertyId); + write_le(i2c_tx_buffer, address); write_le>(i2c_tx_buffer + 2, value); write_le(i2c_tx_buffer + sizeof(i2c_tx_buffer) - 2, json_crc); return I2C_transaction(i2c_addr + num, i2c_tx_buffer, sizeof(i2c_tx_buffer), nullptr, 0); } - /* @brief Write to an endpoint on the ODrive + /* @brief Trigger an parameter-less function on the ODrive * * Usage example: * success = odrive::trigger(0); @@ -155,11 +157,43 @@ namespace odrive { */ template>::value>::type> - bool trigger(uint8_t num) { + bool trigger(uint8_t num, uint16_t address = IPropertyId) { uint8_t i2c_tx_buffer[4]; - write_le(i2c_tx_buffer, IPropertyId); + write_le(i2c_tx_buffer, address); write_le(i2c_tx_buffer + sizeof(i2c_tx_buffer) - 2, json_crc); return I2C_transaction(i2c_addr + num, i2c_tx_buffer, sizeof(i2c_tx_buffer), nullptr, 0); } + template + bool read_axis_property(uint8_t num, uint8_t axis, endpoint_type_t* value) { + return read_property(num, value, IPropertyId + axis * AXIS_ENDPOINT_COUNT); + } + + template + bool write_axis_property(uint8_t num, uint8_t axis, endpoint_type_t value) { + return write_property(num, value, IPropertyId + axis * AXIS_ENDPOINT_COUNT); + } + + + /* @brief Checks if the axis is in the requested state and the error register is clear */ + bool check_axis_state(uint8_t num, uint8_t axis, uint8_t state) { + endpoint_type_t observed_state = 0; + endpoint_type_t observed_error = 0; + if (!read_axis_property(num, axis, &observed_state)) + return false; + if (!read_axis_property(num, axis, &observed_error)) + return false; + return (observed_error == 0) && (observed_state == state); + } + + /* @brief Clears any error state of the specified axis */ + bool clear_errors(uint8_t num, uint8_t axis) { + if (!write_axis_property(num, axis, 0)) + return false; + if (!write_axis_property(num, axis, 0)) + return false; + if (!write_axis_property(num, axis, 0)) + return false; + return true; + } } diff --git a/ArduinoI2C/odrive_endpoints.h b/ArduinoI2C/odrive_endpoints.h index fe5e1b30..3397f364 100644 --- a/ArduinoI2C/odrive_endpoints.h +++ b/ArduinoI2C/odrive_endpoints.h @@ -9,10 +9,11 @@ #ifndef __ODRIVE_ENDPOINTS_HPP #define __ODRIVE_ENDPOINTS_HPP +#define AXIS_ENDPOINT_COUNT 101 namespace odrive { -static constexpr const uint16_t json_crc = 0x7199; +static constexpr const uint16_t json_crc = 0x64cd; enum { VBUS_VOLTAGE = 1, @@ -47,187 +48,195 @@ enum { CONFIG__ENABLE_I2C_INSTEAD_OF_CAN = 30, CONFIG__DC_BUS_UNDERVOLTAGE_TRIP_LEVEL = 31, CONFIG__DC_BUS_OVERVOLTAGE_TRIP_LEVEL = 32, - AXIS0__ERROR = 33, - AXIS0__ENABLE_STEP_DIR = 34, - AXIS0__CURRENT_STATE = 35, - AXIS0__REQUESTED_STATE = 36, - AXIS0__LOOP_COUNTER = 37, - AXIS0__CONFIG__STARTUP_MOTOR_CALIBRATION = 38, - AXIS0__CONFIG__STARTUP_ENCODER_INDEX_SEARCH = 39, - AXIS0__CONFIG__STARTUP_ENCODER_OFFSET_CALIBRATION = 40, - AXIS0__CONFIG__STARTUP_CLOSED_LOOP_CONTROL = 41, - AXIS0__CONFIG__STARTUP_SENSORLESS_CONTROL = 42, - AXIS0__CONFIG__ENABLE_STEP_DIR = 43, - AXIS0__CONFIG__COUNTS_PER_STEP = 44, - AXIS0__CONFIG__RAMP_UP_TIME = 45, - AXIS0__CONFIG__RAMP_UP_DISTANCE = 46, - AXIS0__CONFIG__SPIN_UP_CURRENT = 47, - AXIS0__CONFIG__SPIN_UP_ACCELERATION = 48, - AXIS0__CONFIG__SPIN_UP_TARGET_VEL = 49, - AXIS0__MOTOR__ERROR = 50, - AXIS0__MOTOR__ARMED_STATE = 51, - AXIS0__MOTOR__IS_CALIBRATED = 52, - AXIS0__MOTOR__CURRENT_MEAS_PHB = 53, - AXIS0__MOTOR__CURRENT_MEAS_PHC = 54, - AXIS0__MOTOR__DC_CALIB_PHB = 55, - AXIS0__MOTOR__DC_CALIB_PHC = 56, - AXIS0__MOTOR__PHASE_CURRENT_REV_GAIN = 57, - AXIS0__MOTOR__CURRENT_CONTROL__P_GAIN = 58, - AXIS0__MOTOR__CURRENT_CONTROL__I_GAIN = 59, - AXIS0__MOTOR__CURRENT_CONTROL__V_CURRENT_CONTROL_INTEGRAL_D = 60, - AXIS0__MOTOR__CURRENT_CONTROL__V_CURRENT_CONTROL_INTEGRAL_Q = 61, - AXIS0__MOTOR__CURRENT_CONTROL__IBUS = 62, - AXIS0__MOTOR__CURRENT_CONTROL__FINAL_V_ALPHA = 63, - AXIS0__MOTOR__CURRENT_CONTROL__FINAL_V_BETA = 64, - AXIS0__MOTOR__CURRENT_CONTROL__IQ_SETPOINT = 65, - AXIS0__MOTOR__CURRENT_CONTROL__IQ_MEASURED = 66, - AXIS0__MOTOR__CURRENT_CONTROL__MAX_ALLOWED_CURRENT = 67, - AXIS0__MOTOR__GATE_DRIVER__DRV_FAULT = 68, - AXIS0__MOTOR__TIMING_LOG__TIMING_LOG_GENERAL = 69, - AXIS0__MOTOR__TIMING_LOG__TIMING_LOG_ADC_CB_I = 70, - AXIS0__MOTOR__TIMING_LOG__TIMING_LOG_ADC_CB_DC = 71, - AXIS0__MOTOR__TIMING_LOG__TIMING_LOG_MEAS_R = 72, - AXIS0__MOTOR__TIMING_LOG__TIMING_LOG_MEAS_L = 73, - AXIS0__MOTOR__TIMING_LOG__TIMING_LOG_ENC_CALIB = 74, - AXIS0__MOTOR__TIMING_LOG__TIMING_LOG_IDX_SEARCH = 75, - AXIS0__MOTOR__TIMING_LOG__TIMING_LOG_FOC_VOLTAGE = 76, - AXIS0__MOTOR__TIMING_LOG__TIMING_LOG_FOC_CURRENT = 77, - AXIS0__MOTOR__CONFIG__PRE_CALIBRATED = 78, - AXIS0__MOTOR__CONFIG__POLE_PAIRS = 79, - AXIS0__MOTOR__CONFIG__CALIBRATION_CURRENT = 80, - AXIS0__MOTOR__CONFIG__RESISTANCE_CALIB_MAX_VOLTAGE = 81, - AXIS0__MOTOR__CONFIG__PHASE_INDUCTANCE = 82, - AXIS0__MOTOR__CONFIG__PHASE_RESISTANCE = 83, - AXIS0__MOTOR__CONFIG__DIRECTION = 84, - AXIS0__MOTOR__CONFIG__MOTOR_TYPE = 85, - AXIS0__MOTOR__CONFIG__CURRENT_LIM = 86, - AXIS0__CONTROLLER__POS_SETPOINT = 87, - AXIS0__CONTROLLER__VEL_SETPOINT = 88, - AXIS0__CONTROLLER__VEL_INTEGRATOR_CURRENT = 89, - AXIS0__CONTROLLER__CURRENT_SETPOINT = 90, - AXIS0__CONTROLLER__CONFIG__CONTROL_MODE = 91, - AXIS0__CONTROLLER__CONFIG__POS_GAIN = 92, - AXIS0__CONTROLLER__CONFIG__VEL_GAIN = 93, - AXIS0__CONTROLLER__CONFIG__VEL_INTEGRATOR_GAIN = 94, - AXIS0__CONTROLLER__CONFIG__VEL_LIMIT = 95, - AXIS0__CONTROLLER__START_ANTICOGGING_CALIBRATION = 105, - AXIS0__ENCODER__ERROR = 106, - AXIS0__ENCODER__IS_READY = 107, - AXIS0__ENCODER__INDEX_FOUND = 108, - AXIS0__ENCODER__SHADOW_COUNT = 109, - AXIS0__ENCODER__COUNT_IN_CPR = 110, - AXIS0__ENCODER__OFFSET = 111, - AXIS0__ENCODER__PHASE = 112, - AXIS0__ENCODER__POS_ESTIMATE = 113, - AXIS0__ENCODER__POS_CPR = 114, - AXIS0__ENCODER__PLL_VEL = 115, - AXIS0__ENCODER__PLL_KP = 116, - AXIS0__ENCODER__PLL_KI = 117, - AXIS0__ENCODER__CONFIG__USE_INDEX = 118, - AXIS0__ENCODER__CONFIG__PRE_CALIBRATED = 119, - AXIS0__ENCODER__CONFIG__IDX_SEARCH_SPEED = 120, - AXIS0__ENCODER__CONFIG__CPR = 121, - AXIS0__ENCODER__CONFIG__OFFSET = 122, - AXIS0__ENCODER__CONFIG__CALIB_RANGE = 123, - AXIS0__SENSORLESS_ESTIMATOR__ERROR = 124, - AXIS0__SENSORLESS_ESTIMATOR__PHASE = 125, - AXIS0__SENSORLESS_ESTIMATOR__PLL_POS = 126, - AXIS0__SENSORLESS_ESTIMATOR__PLL_VEL = 127, - AXIS0__SENSORLESS_ESTIMATOR__PLL_KP = 128, - AXIS0__SENSORLESS_ESTIMATOR__PLL_KI = 129, - AXIS1__ERROR = 130, - AXIS1__ENABLE_STEP_DIR = 131, - AXIS1__CURRENT_STATE = 132, - AXIS1__REQUESTED_STATE = 133, - AXIS1__LOOP_COUNTER = 134, - AXIS1__CONFIG__STARTUP_MOTOR_CALIBRATION = 135, - AXIS1__CONFIG__STARTUP_ENCODER_INDEX_SEARCH = 136, - AXIS1__CONFIG__STARTUP_ENCODER_OFFSET_CALIBRATION = 137, - AXIS1__CONFIG__STARTUP_CLOSED_LOOP_CONTROL = 138, - AXIS1__CONFIG__STARTUP_SENSORLESS_CONTROL = 139, - AXIS1__CONFIG__ENABLE_STEP_DIR = 140, - AXIS1__CONFIG__COUNTS_PER_STEP = 141, - AXIS1__CONFIG__RAMP_UP_TIME = 142, - AXIS1__CONFIG__RAMP_UP_DISTANCE = 143, - AXIS1__CONFIG__SPIN_UP_CURRENT = 144, - AXIS1__CONFIG__SPIN_UP_ACCELERATION = 145, - AXIS1__CONFIG__SPIN_UP_TARGET_VEL = 146, - AXIS1__MOTOR__ERROR = 147, - AXIS1__MOTOR__ARMED_STATE = 148, - AXIS1__MOTOR__IS_CALIBRATED = 149, - AXIS1__MOTOR__CURRENT_MEAS_PHB = 150, - AXIS1__MOTOR__CURRENT_MEAS_PHC = 151, - AXIS1__MOTOR__DC_CALIB_PHB = 152, - AXIS1__MOTOR__DC_CALIB_PHC = 153, - AXIS1__MOTOR__PHASE_CURRENT_REV_GAIN = 154, - AXIS1__MOTOR__CURRENT_CONTROL__P_GAIN = 155, - AXIS1__MOTOR__CURRENT_CONTROL__I_GAIN = 156, - AXIS1__MOTOR__CURRENT_CONTROL__V_CURRENT_CONTROL_INTEGRAL_D = 157, - AXIS1__MOTOR__CURRENT_CONTROL__V_CURRENT_CONTROL_INTEGRAL_Q = 158, - AXIS1__MOTOR__CURRENT_CONTROL__IBUS = 159, - AXIS1__MOTOR__CURRENT_CONTROL__FINAL_V_ALPHA = 160, - AXIS1__MOTOR__CURRENT_CONTROL__FINAL_V_BETA = 161, - AXIS1__MOTOR__CURRENT_CONTROL__IQ_SETPOINT = 162, - AXIS1__MOTOR__CURRENT_CONTROL__IQ_MEASURED = 163, - AXIS1__MOTOR__CURRENT_CONTROL__MAX_ALLOWED_CURRENT = 164, - AXIS1__MOTOR__GATE_DRIVER__DRV_FAULT = 165, - AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_GENERAL = 166, - AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_ADC_CB_I = 167, - AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_ADC_CB_DC = 168, - AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_MEAS_R = 169, - AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_MEAS_L = 170, - AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_ENC_CALIB = 171, - AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_IDX_SEARCH = 172, - AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_FOC_VOLTAGE = 173, - AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_FOC_CURRENT = 174, - AXIS1__MOTOR__CONFIG__PRE_CALIBRATED = 175, - AXIS1__MOTOR__CONFIG__POLE_PAIRS = 176, - AXIS1__MOTOR__CONFIG__CALIBRATION_CURRENT = 177, - AXIS1__MOTOR__CONFIG__RESISTANCE_CALIB_MAX_VOLTAGE = 178, - AXIS1__MOTOR__CONFIG__PHASE_INDUCTANCE = 179, - AXIS1__MOTOR__CONFIG__PHASE_RESISTANCE = 180, - AXIS1__MOTOR__CONFIG__DIRECTION = 181, - AXIS1__MOTOR__CONFIG__MOTOR_TYPE = 182, - AXIS1__MOTOR__CONFIG__CURRENT_LIM = 183, - AXIS1__CONTROLLER__POS_SETPOINT = 184, - AXIS1__CONTROLLER__VEL_SETPOINT = 185, - AXIS1__CONTROLLER__VEL_INTEGRATOR_CURRENT = 186, - AXIS1__CONTROLLER__CURRENT_SETPOINT = 187, - AXIS1__CONTROLLER__CONFIG__CONTROL_MODE = 188, - AXIS1__CONTROLLER__CONFIG__POS_GAIN = 189, - AXIS1__CONTROLLER__CONFIG__VEL_GAIN = 190, - AXIS1__CONTROLLER__CONFIG__VEL_INTEGRATOR_GAIN = 191, - AXIS1__CONTROLLER__CONFIG__VEL_LIMIT = 192, - AXIS1__CONTROLLER__START_ANTICOGGING_CALIBRATION = 202, - AXIS1__ENCODER__ERROR = 203, - AXIS1__ENCODER__IS_READY = 204, - AXIS1__ENCODER__INDEX_FOUND = 205, - AXIS1__ENCODER__SHADOW_COUNT = 206, - AXIS1__ENCODER__COUNT_IN_CPR = 207, - AXIS1__ENCODER__OFFSET = 208, - AXIS1__ENCODER__PHASE = 209, - AXIS1__ENCODER__POS_ESTIMATE = 210, - AXIS1__ENCODER__POS_CPR = 211, - AXIS1__ENCODER__PLL_VEL = 212, - AXIS1__ENCODER__PLL_KP = 213, - AXIS1__ENCODER__PLL_KI = 214, - AXIS1__ENCODER__CONFIG__USE_INDEX = 215, - AXIS1__ENCODER__CONFIG__PRE_CALIBRATED = 216, - AXIS1__ENCODER__CONFIG__IDX_SEARCH_SPEED = 217, - AXIS1__ENCODER__CONFIG__CPR = 218, - AXIS1__ENCODER__CONFIG__OFFSET = 219, - AXIS1__ENCODER__CONFIG__CALIB_RANGE = 220, - AXIS1__SENSORLESS_ESTIMATOR__ERROR = 221, - AXIS1__SENSORLESS_ESTIMATOR__PHASE = 222, - AXIS1__SENSORLESS_ESTIMATOR__PLL_POS = 223, - AXIS1__SENSORLESS_ESTIMATOR__PLL_VEL = 224, - AXIS1__SENSORLESS_ESTIMATOR__PLL_KP = 225, - AXIS1__SENSORLESS_ESTIMATOR__PLL_KI = 226, - TEST_PROPERTY = 227, - SAVE_CONFIGURATION = 234, - ERASE_CONFIGURATION = 235, - REBOOT = 236, - ENTER_DFU_MODE = 237, + AXIS__ERROR = 33, + AXIS__ENABLE_STEP_DIR = 34, + AXIS__CURRENT_STATE = 35, + AXIS__REQUESTED_STATE = 36, + AXIS__LOOP_COUNTER = 37, + AXIS__CONFIG__STARTUP_MOTOR_CALIBRATION = 38, + AXIS__CONFIG__STARTUP_ENCODER_INDEX_SEARCH = 39, + AXIS__CONFIG__STARTUP_ENCODER_OFFSET_CALIBRATION = 40, + AXIS__CONFIG__STARTUP_CLOSED_LOOP_CONTROL = 41, + AXIS__CONFIG__STARTUP_SENSORLESS_CONTROL = 42, + AXIS__CONFIG__ENABLE_STEP_DIR = 43, + AXIS__CONFIG__COUNTS_PER_STEP = 44, + AXIS__CONFIG__RAMP_UP_TIME = 45, + AXIS__CONFIG__RAMP_UP_DISTANCE = 46, + AXIS__CONFIG__SPIN_UP_CURRENT = 47, + AXIS__CONFIG__SPIN_UP_ACCELERATION = 48, + AXIS__CONFIG__SPIN_UP_TARGET_VEL = 49, + AXIS__MOTOR__ERROR = 50, + AXIS__MOTOR__ARMED_STATE = 51, + AXIS__MOTOR__IS_CALIBRATED = 52, + AXIS__MOTOR__CURRENT_MEAS_PHB = 53, + AXIS__MOTOR__CURRENT_MEAS_PHC = 54, + AXIS__MOTOR__DC_CALIB_PHB = 55, + AXIS__MOTOR__DC_CALIB_PHC = 56, + AXIS__MOTOR__PHASE_CURRENT_REV_GAIN = 57, + AXIS__MOTOR__CURRENT_CONTROL__P_GAIN = 58, + AXIS__MOTOR__CURRENT_CONTROL__I_GAIN = 59, + AXIS__MOTOR__CURRENT_CONTROL__V_CURRENT_CONTROL_INTEGRAL_D = 60, + AXIS__MOTOR__CURRENT_CONTROL__V_CURRENT_CONTROL_INTEGRAL_Q = 61, + AXIS__MOTOR__CURRENT_CONTROL__IBUS = 62, + AXIS__MOTOR__CURRENT_CONTROL__FINAL_V_ALPHA = 63, + AXIS__MOTOR__CURRENT_CONTROL__FINAL_V_BETA = 64, + AXIS__MOTOR__CURRENT_CONTROL__IQ_SETPOINT = 65, + AXIS__MOTOR__CURRENT_CONTROL__IQ_MEASURED = 66, + AXIS__MOTOR__CURRENT_CONTROL__MAX_ALLOWED_CURRENT = 67, + AXIS__MOTOR__GATE_DRIVER__DRV_FAULT = 68, + AXIS__MOTOR__TIMING_LOG__TIMING_LOG_GENERAL = 69, + AXIS__MOTOR__TIMING_LOG__TIMING_LOG_ADC_CB_I = 70, + AXIS__MOTOR__TIMING_LOG__TIMING_LOG_ADC_CB_DC = 71, + AXIS__MOTOR__TIMING_LOG__TIMING_LOG_MEAS_R = 72, + AXIS__MOTOR__TIMING_LOG__TIMING_LOG_MEAS_L = 73, + AXIS__MOTOR__TIMING_LOG__TIMING_LOG_ENC_CALIB = 74, + AXIS__MOTOR__TIMING_LOG__TIMING_LOG_IDX_SEARCH = 75, + AXIS__MOTOR__TIMING_LOG__TIMING_LOG_FOC_VOLTAGE = 76, + AXIS__MOTOR__TIMING_LOG__TIMING_LOG_FOC_CURRENT = 77, + AXIS__MOTOR__CONFIG__PRE_CALIBRATED = 78, + AXIS__MOTOR__CONFIG__POLE_PAIRS = 79, + AXIS__MOTOR__CONFIG__CALIBRATION_CURRENT = 80, + AXIS__MOTOR__CONFIG__RESISTANCE_CALIB_MAX_VOLTAGE = 81, + AXIS__MOTOR__CONFIG__PHASE_INDUCTANCE = 82, + AXIS__MOTOR__CONFIG__PHASE_RESISTANCE = 83, + AXIS__MOTOR__CONFIG__DIRECTION = 84, + AXIS__MOTOR__CONFIG__MOTOR_TYPE = 85, + AXIS__MOTOR__CONFIG__CURRENT_LIM = 86, + AXIS__CONTROLLER__POS_SETPOINT = 87, + AXIS__CONTROLLER__VEL_SETPOINT = 88, + AXIS__CONTROLLER__VEL_INTEGRATOR_CURRENT = 89, + AXIS__CONTROLLER__CURRENT_SETPOINT = 90, + AXIS__CONTROLLER__CONFIG__CONTROL_MODE = 91, + AXIS__CONTROLLER__CONFIG__POS_GAIN = 92, + AXIS__CONTROLLER__CONFIG__VEL_GAIN = 93, + AXIS__CONTROLLER__CONFIG__VEL_INTEGRATOR_GAIN = 94, + AXIS__CONTROLLER__CONFIG__VEL_LIMIT = 95, + AXIS__CONTROLLER__START_ANTICOGGING_CALIBRATION = 105, + AXIS__ENCODER__ERROR = 106, + AXIS__ENCODER__IS_READY = 107, + AXIS__ENCODER__INDEX_FOUND = 108, + AXIS__ENCODER__SHADOW_COUNT = 109, + AXIS__ENCODER__COUNT_IN_CPR = 110, + AXIS__ENCODER__OFFSET = 111, + AXIS__ENCODER__INTERPOLATION = 112, + AXIS__ENCODER__PHASE = 113, + AXIS__ENCODER__POS_ESTIMATE = 114, + AXIS__ENCODER__POS_CPR = 115, + AXIS__ENCODER__HALL_STATE = 116, + AXIS__ENCODER__PLL_VEL = 117, + AXIS__ENCODER__PLL_KP = 118, + AXIS__ENCODER__PLL_KI = 119, + AXIS__ENCODER__CONFIG__MODE = 120, + AXIS__ENCODER__CONFIG__USE_INDEX = 121, + AXIS__ENCODER__CONFIG__PRE_CALIBRATED = 122, + AXIS__ENCODER__CONFIG__IDX_SEARCH_SPEED = 123, + AXIS__ENCODER__CONFIG__CPR = 124, + AXIS__ENCODER__CONFIG__OFFSET = 125, + AXIS__ENCODER__CONFIG__OFFSET_FLOAT = 126, + AXIS__ENCODER__CONFIG__CALIB_RANGE = 127, + AXIS__SENSORLESS_ESTIMATOR__ERROR = 128, + AXIS__SENSORLESS_ESTIMATOR__PHASE = 129, + AXIS__SENSORLESS_ESTIMATOR__PLL_POS = 130, + AXIS__SENSORLESS_ESTIMATOR__PLL_VEL = 131, + AXIS__SENSORLESS_ESTIMATOR__PLL_KP = 132, + AXIS__SENSORLESS_ESTIMATOR__PLL_KI = 133, + AXIS1__ERROR = 134, + AXIS1__ENABLE_STEP_DIR = 135, + AXIS1__CURRENT_STATE = 136, + AXIS1__REQUESTED_STATE = 137, + AXIS1__LOOP_COUNTER = 138, + AXIS1__CONFIG__STARTUP_MOTOR_CALIBRATION = 139, + AXIS1__CONFIG__STARTUP_ENCODER_INDEX_SEARCH = 140, + AXIS1__CONFIG__STARTUP_ENCODER_OFFSET_CALIBRATION = 141, + AXIS1__CONFIG__STARTUP_CLOSED_LOOP_CONTROL = 142, + AXIS1__CONFIG__STARTUP_SENSORLESS_CONTROL = 143, + AXIS1__CONFIG__ENABLE_STEP_DIR = 144, + AXIS1__CONFIG__COUNTS_PER_STEP = 145, + AXIS1__CONFIG__RAMP_UP_TIME = 146, + AXIS1__CONFIG__RAMP_UP_DISTANCE = 147, + AXIS1__CONFIG__SPIN_UP_CURRENT = 148, + AXIS1__CONFIG__SPIN_UP_ACCELERATION = 149, + AXIS1__CONFIG__SPIN_UP_TARGET_VEL = 150, + AXIS1__MOTOR__ERROR = 151, + AXIS1__MOTOR__ARMED_STATE = 152, + AXIS1__MOTOR__IS_CALIBRATED = 153, + AXIS1__MOTOR__CURRENT_MEAS_PHB = 154, + AXIS1__MOTOR__CURRENT_MEAS_PHC = 155, + AXIS1__MOTOR__DC_CALIB_PHB = 156, + AXIS1__MOTOR__DC_CALIB_PHC = 157, + AXIS1__MOTOR__PHASE_CURRENT_REV_GAIN = 158, + AXIS1__MOTOR__CURRENT_CONTROL__P_GAIN = 159, + AXIS1__MOTOR__CURRENT_CONTROL__I_GAIN = 160, + AXIS1__MOTOR__CURRENT_CONTROL__V_CURRENT_CONTROL_INTEGRAL_D = 161, + AXIS1__MOTOR__CURRENT_CONTROL__V_CURRENT_CONTROL_INTEGRAL_Q = 162, + AXIS1__MOTOR__CURRENT_CONTROL__IBUS = 163, + AXIS1__MOTOR__CURRENT_CONTROL__FINAL_V_ALPHA = 164, + AXIS1__MOTOR__CURRENT_CONTROL__FINAL_V_BETA = 165, + AXIS1__MOTOR__CURRENT_CONTROL__IQ_SETPOINT = 166, + AXIS1__MOTOR__CURRENT_CONTROL__IQ_MEASURED = 167, + AXIS1__MOTOR__CURRENT_CONTROL__MAX_ALLOWED_CURRENT = 168, + AXIS1__MOTOR__GATE_DRIVER__DRV_FAULT = 169, + AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_GENERAL = 170, + AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_ADC_CB_I = 171, + AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_ADC_CB_DC = 172, + AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_MEAS_R = 173, + AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_MEAS_L = 174, + AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_ENC_CALIB = 175, + AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_IDX_SEARCH = 176, + AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_FOC_VOLTAGE = 177, + AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_FOC_CURRENT = 178, + AXIS1__MOTOR__CONFIG__PRE_CALIBRATED = 179, + AXIS1__MOTOR__CONFIG__POLE_PAIRS = 180, + AXIS1__MOTOR__CONFIG__CALIBRATION_CURRENT = 181, + AXIS1__MOTOR__CONFIG__RESISTANCE_CALIB_MAX_VOLTAGE = 182, + AXIS1__MOTOR__CONFIG__PHASE_INDUCTANCE = 183, + AXIS1__MOTOR__CONFIG__PHASE_RESISTANCE = 184, + AXIS1__MOTOR__CONFIG__DIRECTION = 185, + AXIS1__MOTOR__CONFIG__MOTOR_TYPE = 186, + AXIS1__MOTOR__CONFIG__CURRENT_LIM = 187, + AXIS1__CONTROLLER__POS_SETPOINT = 188, + AXIS1__CONTROLLER__VEL_SETPOINT = 189, + AXIS1__CONTROLLER__VEL_INTEGRATOR_CURRENT = 190, + AXIS1__CONTROLLER__CURRENT_SETPOINT = 191, + AXIS1__CONTROLLER__CONFIG__CONTROL_MODE = 192, + AXIS1__CONTROLLER__CONFIG__POS_GAIN = 193, + AXIS1__CONTROLLER__CONFIG__VEL_GAIN = 194, + AXIS1__CONTROLLER__CONFIG__VEL_INTEGRATOR_GAIN = 195, + AXIS1__CONTROLLER__CONFIG__VEL_LIMIT = 196, + AXIS1__CONTROLLER__START_ANTICOGGING_CALIBRATION = 206, + AXIS1__ENCODER__ERROR = 207, + AXIS1__ENCODER__IS_READY = 208, + AXIS1__ENCODER__INDEX_FOUND = 209, + AXIS1__ENCODER__SHADOW_COUNT = 210, + AXIS1__ENCODER__COUNT_IN_CPR = 211, + AXIS1__ENCODER__OFFSET = 212, + AXIS1__ENCODER__INTERPOLATION = 213, + AXIS1__ENCODER__PHASE = 214, + AXIS1__ENCODER__POS_ESTIMATE = 215, + AXIS1__ENCODER__POS_CPR = 216, + AXIS1__ENCODER__HALL_STATE = 217, + AXIS1__ENCODER__PLL_VEL = 218, + AXIS1__ENCODER__PLL_KP = 219, + AXIS1__ENCODER__PLL_KI = 220, + AXIS1__ENCODER__CONFIG__MODE = 221, + AXIS1__ENCODER__CONFIG__USE_INDEX = 222, + AXIS1__ENCODER__CONFIG__PRE_CALIBRATED = 223, + AXIS1__ENCODER__CONFIG__IDX_SEARCH_SPEED = 224, + AXIS1__ENCODER__CONFIG__CPR = 225, + AXIS1__ENCODER__CONFIG__OFFSET = 226, + AXIS1__ENCODER__CONFIG__OFFSET_FLOAT = 227, + AXIS1__ENCODER__CONFIG__CALIB_RANGE = 228, + AXIS1__SENSORLESS_ESTIMATOR__ERROR = 229, + AXIS1__SENSORLESS_ESTIMATOR__PHASE = 230, + AXIS1__SENSORLESS_ESTIMATOR__PLL_POS = 231, + AXIS1__SENSORLESS_ESTIMATOR__PLL_VEL = 232, + AXIS1__SENSORLESS_ESTIMATOR__PLL_KP = 233, + AXIS1__SENSORLESS_ESTIMATOR__PLL_KI = 234, + TEST_PROPERTY = 235, + SAVE_CONFIGURATION = 242, + ERASE_CONFIGURATION = 243, + REBOOT = 244, + ENTER_DFU_MODE = 245, }; template @@ -265,94 +274,98 @@ template<> struct endpoint_type { typedef bool type; }; template<> struct endpoint_type { typedef bool type; }; template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef uint8_t type; }; -template<> struct endpoint_type { typedef uint8_t type; }; -template<> struct endpoint_type { typedef uint32_t type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef uint8_t type; }; -template<> struct endpoint_type { typedef uint8_t type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef int32_t type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef int32_t type; }; -template<> struct endpoint_type { typedef uint8_t type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef uint8_t type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef void type; }; -template<> struct endpoint_type { typedef uint8_t type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef int32_t type; }; -template<> struct endpoint_type { typedef int32_t type; }; -template<> struct endpoint_type { typedef int32_t type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef int32_t type; }; -template<> struct endpoint_type { typedef int32_t type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef uint8_t type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef void type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef bool type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef uint16_t type; }; template<> struct endpoint_type { typedef bool type; }; template<> struct endpoint_type { typedef uint8_t type; }; @@ -370,7 +383,7 @@ template<> struct endpoint_type { typedef float template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef uint8_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; template<> struct endpoint_type { typedef uint8_t type; }; template<> struct endpoint_type { typedef bool type; }; template<> struct endpoint_type { typedef float type; }; @@ -423,17 +436,21 @@ template<> struct endpoint_type { typedef bool type template<> struct endpoint_type { typedef int32_t type; }; template<> struct endpoint_type { typedef int32_t type; }; template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint8_t type; }; template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint8_t type; }; template<> struct endpoint_type { typedef bool type; }; template<> struct endpoint_type { typedef bool type; }; template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef int32_t type; }; template<> struct endpoint_type { typedef int32_t type; }; +template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef uint8_t type; }; template<> struct endpoint_type { typedef float type; }; From d6496ca5748e3159c88a836197bad35dca2a71da Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 27 Apr 2018 15:04:43 -0700 Subject: [PATCH 164/215] save offset and set default cpr to 72 --- Firmware/MotorControl/encoder.cpp | 1 + Firmware/MotorControl/encoder.hpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 07a6b3a1..5bf57c1c 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -215,6 +215,7 @@ bool Encoder::run_offset_calibration() { return false; offset_ = encvaluesum / (num_steps * 2); + config_.offset = offset_; int32_t residual = encvaluesum - ((int64_t)offset_ * (int64_t)(num_steps * 2)); config_.offset_float = (float)residual / (float)(num_steps * 2); is_ready_ = true; diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 4dc67013..fafd0cc5 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -30,7 +30,7 @@ public: // In this case the encoder will enter ready // state as soon as the index is found. float idx_search_speed = 10.0f; // [rad/s electrical] - int32_t cpr = (2048 * 4); // Default resolution of CUI-AMT102 encoder, + int32_t cpr = 72; //(2048 * 4); // Default resolution of CUI-AMT102 encoder, int32_t offset = 0; // If pre_calibrated is true, this is copied into encoder.offset_ once // index search succeeds float offset_float = 0.0f; // Sub-count phase alignment offset From d9be12d4003007f309b177efff9bbc558c545c00 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 27 Apr 2018 15:59:12 -0700 Subject: [PATCH 165/215] Arduino: only try to config if device is not already configured --- ArduinoI2C/ArduinoI2C.ino | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ArduinoI2C/ArduinoI2C.ino b/ArduinoI2C/ArduinoI2C.ino index e5b852ea..b443642c 100644 --- a/ArduinoI2C/ArduinoI2C.ino +++ b/ArduinoI2C/ArduinoI2C.ino @@ -34,6 +34,16 @@ int set_and_save_configuration(uint8_t odrive_num, uint8_t axis_num) { if (!success) return __LINE__; + // select hall effect mode + bool user_config_loaded = false; + success = odrive::read_property(odrive_num, &user_config_loaded); + if (!success) + return __LINE__; + if (user_config_loaded) { + Serial.println("ODrive already configured"); + return 0; + } + // select hall effect mode success = odrive::write_axis_property(odrive_num, axis_num, 1); if (!success) @@ -108,6 +118,7 @@ bool do_setup = true; void setup() { Wire.begin(); // join i2c bus (address optional for master) Serial.begin(9600); + Serial.println("Hello World!"); if (do_setup) { Serial.println("Starting ODrive setup..."); From 64324d17d4fd13c56d482823fab19c50554759f1 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 27 Apr 2018 17:36:47 -0700 Subject: [PATCH 166/215] update header file generator to address axes by number --- ArduinoI2C/odrive.h | 4 +- ArduinoI2C/odrive_endpoints.h | 460 +++++++++--------------------- tools/odrive/code_generator.py | 20 +- tools/odrive_header_template.h.in | 14 +- 4 files changed, 173 insertions(+), 325 deletions(-) diff --git a/ArduinoI2C/odrive.h b/ArduinoI2C/odrive.h index 9458af55..bce435f1 100644 --- a/ArduinoI2C/odrive.h +++ b/ArduinoI2C/odrive.h @@ -166,12 +166,12 @@ namespace odrive { template bool read_axis_property(uint8_t num, uint8_t axis, endpoint_type_t* value) { - return read_property(num, value, IPropertyId + axis * AXIS_ENDPOINT_COUNT); + return read_property(num, value, IPropertyId + axis * per_axis_offset); } template bool write_axis_property(uint8_t num, uint8_t axis, endpoint_type_t value) { - return write_property(num, value, IPropertyId + axis * AXIS_ENDPOINT_COUNT); + return write_property(num, value, IPropertyId + axis * per_axis_offset); } diff --git a/ArduinoI2C/odrive_endpoints.h b/ArduinoI2C/odrive_endpoints.h index 3397f364..765da575 100644 --- a/ArduinoI2C/odrive_endpoints.h +++ b/ArduinoI2C/odrive_endpoints.h @@ -9,234 +9,145 @@ #ifndef __ODRIVE_ENDPOINTS_HPP #define __ODRIVE_ENDPOINTS_HPP -#define AXIS_ENDPOINT_COUNT 101 namespace odrive { static constexpr const uint16_t json_crc = 0x64cd; +static constexpr const uint16_t per_axis_offset = 101; + enum { - VBUS_VOLTAGE = 1, - SERIAL_NUMBER = 2, - HW_VERSION_MAJOR = 3, - HW_VERSION_MINOR = 4, - HW_VERSION_VARIANT = 5, - FW_VERSION_MAJOR = 6, - FW_VERSION_MINOR = 7, - FW_VERSION_REVISION = 8, - FW_VERSION_UNRELEASED = 9, - USER_CONFIG_LOADED = 10, - BRAKE_RESISTOR_ARMED = 11, - SYSTEM_STATS__UPTIME = 12, - SYSTEM_STATS__MIN_HEAP_SPACE = 13, - SYSTEM_STATS__MIN_STACK_SPACE_AXIS0 = 14, - SYSTEM_STATS__MIN_STACK_SPACE_AXIS1 = 15, - SYSTEM_STATS__MIN_STACK_SPACE_COMMS = 16, - SYSTEM_STATS__MIN_STACK_SPACE_USB = 17, - SYSTEM_STATS__MIN_STACK_SPACE_UART = 18, - SYSTEM_STATS__MIN_STACK_SPACE_USB_IRQ = 19, - SYSTEM_STATS__MIN_STACK_SPACE_STARTUP = 20, - SYSTEM_STATS__USB__RX_CNT = 21, - SYSTEM_STATS__USB__TX_CNT = 22, - SYSTEM_STATS__USB__TX_OVERRUN_CNT = 23, - SYSTEM_STATS__I2C__ADDR = 24, - SYSTEM_STATS__I2C__ADDR_MATCH_CNT = 25, - SYSTEM_STATS__I2C__RX_CNT = 26, - SYSTEM_STATS__I2C__ERROR_CNT = 27, - CONFIG__BRAKE_RESISTANCE = 28, - CONFIG__ENABLE_UART = 29, - CONFIG__ENABLE_I2C_INSTEAD_OF_CAN = 30, - CONFIG__DC_BUS_UNDERVOLTAGE_TRIP_LEVEL = 31, - CONFIG__DC_BUS_OVERVOLTAGE_TRIP_LEVEL = 32, - AXIS__ERROR = 33, - AXIS__ENABLE_STEP_DIR = 34, - AXIS__CURRENT_STATE = 35, - AXIS__REQUESTED_STATE = 36, - AXIS__LOOP_COUNTER = 37, - AXIS__CONFIG__STARTUP_MOTOR_CALIBRATION = 38, - AXIS__CONFIG__STARTUP_ENCODER_INDEX_SEARCH = 39, - AXIS__CONFIG__STARTUP_ENCODER_OFFSET_CALIBRATION = 40, - AXIS__CONFIG__STARTUP_CLOSED_LOOP_CONTROL = 41, - AXIS__CONFIG__STARTUP_SENSORLESS_CONTROL = 42, - AXIS__CONFIG__ENABLE_STEP_DIR = 43, - AXIS__CONFIG__COUNTS_PER_STEP = 44, - AXIS__CONFIG__RAMP_UP_TIME = 45, - AXIS__CONFIG__RAMP_UP_DISTANCE = 46, - AXIS__CONFIG__SPIN_UP_CURRENT = 47, - AXIS__CONFIG__SPIN_UP_ACCELERATION = 48, - AXIS__CONFIG__SPIN_UP_TARGET_VEL = 49, - AXIS__MOTOR__ERROR = 50, - AXIS__MOTOR__ARMED_STATE = 51, - AXIS__MOTOR__IS_CALIBRATED = 52, - AXIS__MOTOR__CURRENT_MEAS_PHB = 53, - AXIS__MOTOR__CURRENT_MEAS_PHC = 54, - AXIS__MOTOR__DC_CALIB_PHB = 55, - AXIS__MOTOR__DC_CALIB_PHC = 56, - AXIS__MOTOR__PHASE_CURRENT_REV_GAIN = 57, - AXIS__MOTOR__CURRENT_CONTROL__P_GAIN = 58, - AXIS__MOTOR__CURRENT_CONTROL__I_GAIN = 59, - AXIS__MOTOR__CURRENT_CONTROL__V_CURRENT_CONTROL_INTEGRAL_D = 60, - AXIS__MOTOR__CURRENT_CONTROL__V_CURRENT_CONTROL_INTEGRAL_Q = 61, - AXIS__MOTOR__CURRENT_CONTROL__IBUS = 62, - AXIS__MOTOR__CURRENT_CONTROL__FINAL_V_ALPHA = 63, - AXIS__MOTOR__CURRENT_CONTROL__FINAL_V_BETA = 64, - AXIS__MOTOR__CURRENT_CONTROL__IQ_SETPOINT = 65, - AXIS__MOTOR__CURRENT_CONTROL__IQ_MEASURED = 66, - AXIS__MOTOR__CURRENT_CONTROL__MAX_ALLOWED_CURRENT = 67, - AXIS__MOTOR__GATE_DRIVER__DRV_FAULT = 68, - AXIS__MOTOR__TIMING_LOG__TIMING_LOG_GENERAL = 69, - AXIS__MOTOR__TIMING_LOG__TIMING_LOG_ADC_CB_I = 70, - AXIS__MOTOR__TIMING_LOG__TIMING_LOG_ADC_CB_DC = 71, - AXIS__MOTOR__TIMING_LOG__TIMING_LOG_MEAS_R = 72, - AXIS__MOTOR__TIMING_LOG__TIMING_LOG_MEAS_L = 73, - AXIS__MOTOR__TIMING_LOG__TIMING_LOG_ENC_CALIB = 74, - AXIS__MOTOR__TIMING_LOG__TIMING_LOG_IDX_SEARCH = 75, - AXIS__MOTOR__TIMING_LOG__TIMING_LOG_FOC_VOLTAGE = 76, - AXIS__MOTOR__TIMING_LOG__TIMING_LOG_FOC_CURRENT = 77, - AXIS__MOTOR__CONFIG__PRE_CALIBRATED = 78, - AXIS__MOTOR__CONFIG__POLE_PAIRS = 79, - AXIS__MOTOR__CONFIG__CALIBRATION_CURRENT = 80, - AXIS__MOTOR__CONFIG__RESISTANCE_CALIB_MAX_VOLTAGE = 81, - AXIS__MOTOR__CONFIG__PHASE_INDUCTANCE = 82, - AXIS__MOTOR__CONFIG__PHASE_RESISTANCE = 83, - AXIS__MOTOR__CONFIG__DIRECTION = 84, - AXIS__MOTOR__CONFIG__MOTOR_TYPE = 85, - AXIS__MOTOR__CONFIG__CURRENT_LIM = 86, - AXIS__CONTROLLER__POS_SETPOINT = 87, - AXIS__CONTROLLER__VEL_SETPOINT = 88, - AXIS__CONTROLLER__VEL_INTEGRATOR_CURRENT = 89, - AXIS__CONTROLLER__CURRENT_SETPOINT = 90, - AXIS__CONTROLLER__CONFIG__CONTROL_MODE = 91, - AXIS__CONTROLLER__CONFIG__POS_GAIN = 92, - AXIS__CONTROLLER__CONFIG__VEL_GAIN = 93, - AXIS__CONTROLLER__CONFIG__VEL_INTEGRATOR_GAIN = 94, - AXIS__CONTROLLER__CONFIG__VEL_LIMIT = 95, - AXIS__CONTROLLER__START_ANTICOGGING_CALIBRATION = 105, - AXIS__ENCODER__ERROR = 106, - AXIS__ENCODER__IS_READY = 107, - AXIS__ENCODER__INDEX_FOUND = 108, - AXIS__ENCODER__SHADOW_COUNT = 109, - AXIS__ENCODER__COUNT_IN_CPR = 110, - AXIS__ENCODER__OFFSET = 111, - AXIS__ENCODER__INTERPOLATION = 112, - AXIS__ENCODER__PHASE = 113, - AXIS__ENCODER__POS_ESTIMATE = 114, - AXIS__ENCODER__POS_CPR = 115, - AXIS__ENCODER__HALL_STATE = 116, - AXIS__ENCODER__PLL_VEL = 117, - AXIS__ENCODER__PLL_KP = 118, - AXIS__ENCODER__PLL_KI = 119, - AXIS__ENCODER__CONFIG__MODE = 120, - AXIS__ENCODER__CONFIG__USE_INDEX = 121, - AXIS__ENCODER__CONFIG__PRE_CALIBRATED = 122, - AXIS__ENCODER__CONFIG__IDX_SEARCH_SPEED = 123, - AXIS__ENCODER__CONFIG__CPR = 124, - AXIS__ENCODER__CONFIG__OFFSET = 125, - AXIS__ENCODER__CONFIG__OFFSET_FLOAT = 126, - AXIS__ENCODER__CONFIG__CALIB_RANGE = 127, - AXIS__SENSORLESS_ESTIMATOR__ERROR = 128, - AXIS__SENSORLESS_ESTIMATOR__PHASE = 129, - AXIS__SENSORLESS_ESTIMATOR__PLL_POS = 130, - AXIS__SENSORLESS_ESTIMATOR__PLL_VEL = 131, - AXIS__SENSORLESS_ESTIMATOR__PLL_KP = 132, - AXIS__SENSORLESS_ESTIMATOR__PLL_KI = 133, - AXIS1__ERROR = 134, - AXIS1__ENABLE_STEP_DIR = 135, - AXIS1__CURRENT_STATE = 136, - AXIS1__REQUESTED_STATE = 137, - AXIS1__LOOP_COUNTER = 138, - AXIS1__CONFIG__STARTUP_MOTOR_CALIBRATION = 139, - AXIS1__CONFIG__STARTUP_ENCODER_INDEX_SEARCH = 140, - AXIS1__CONFIG__STARTUP_ENCODER_OFFSET_CALIBRATION = 141, - AXIS1__CONFIG__STARTUP_CLOSED_LOOP_CONTROL = 142, - AXIS1__CONFIG__STARTUP_SENSORLESS_CONTROL = 143, - AXIS1__CONFIG__ENABLE_STEP_DIR = 144, - AXIS1__CONFIG__COUNTS_PER_STEP = 145, - AXIS1__CONFIG__RAMP_UP_TIME = 146, - AXIS1__CONFIG__RAMP_UP_DISTANCE = 147, - AXIS1__CONFIG__SPIN_UP_CURRENT = 148, - AXIS1__CONFIG__SPIN_UP_ACCELERATION = 149, - AXIS1__CONFIG__SPIN_UP_TARGET_VEL = 150, - AXIS1__MOTOR__ERROR = 151, - AXIS1__MOTOR__ARMED_STATE = 152, - AXIS1__MOTOR__IS_CALIBRATED = 153, - AXIS1__MOTOR__CURRENT_MEAS_PHB = 154, - AXIS1__MOTOR__CURRENT_MEAS_PHC = 155, - AXIS1__MOTOR__DC_CALIB_PHB = 156, - AXIS1__MOTOR__DC_CALIB_PHC = 157, - AXIS1__MOTOR__PHASE_CURRENT_REV_GAIN = 158, - AXIS1__MOTOR__CURRENT_CONTROL__P_GAIN = 159, - AXIS1__MOTOR__CURRENT_CONTROL__I_GAIN = 160, - AXIS1__MOTOR__CURRENT_CONTROL__V_CURRENT_CONTROL_INTEGRAL_D = 161, - AXIS1__MOTOR__CURRENT_CONTROL__V_CURRENT_CONTROL_INTEGRAL_Q = 162, - AXIS1__MOTOR__CURRENT_CONTROL__IBUS = 163, - AXIS1__MOTOR__CURRENT_CONTROL__FINAL_V_ALPHA = 164, - AXIS1__MOTOR__CURRENT_CONTROL__FINAL_V_BETA = 165, - AXIS1__MOTOR__CURRENT_CONTROL__IQ_SETPOINT = 166, - AXIS1__MOTOR__CURRENT_CONTROL__IQ_MEASURED = 167, - AXIS1__MOTOR__CURRENT_CONTROL__MAX_ALLOWED_CURRENT = 168, - AXIS1__MOTOR__GATE_DRIVER__DRV_FAULT = 169, - AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_GENERAL = 170, - AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_ADC_CB_I = 171, - AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_ADC_CB_DC = 172, - AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_MEAS_R = 173, - AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_MEAS_L = 174, - AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_ENC_CALIB = 175, - AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_IDX_SEARCH = 176, - AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_FOC_VOLTAGE = 177, - AXIS1__MOTOR__TIMING_LOG__TIMING_LOG_FOC_CURRENT = 178, - AXIS1__MOTOR__CONFIG__PRE_CALIBRATED = 179, - AXIS1__MOTOR__CONFIG__POLE_PAIRS = 180, - AXIS1__MOTOR__CONFIG__CALIBRATION_CURRENT = 181, - AXIS1__MOTOR__CONFIG__RESISTANCE_CALIB_MAX_VOLTAGE = 182, - AXIS1__MOTOR__CONFIG__PHASE_INDUCTANCE = 183, - AXIS1__MOTOR__CONFIG__PHASE_RESISTANCE = 184, - AXIS1__MOTOR__CONFIG__DIRECTION = 185, - AXIS1__MOTOR__CONFIG__MOTOR_TYPE = 186, - AXIS1__MOTOR__CONFIG__CURRENT_LIM = 187, - AXIS1__CONTROLLER__POS_SETPOINT = 188, - AXIS1__CONTROLLER__VEL_SETPOINT = 189, - AXIS1__CONTROLLER__VEL_INTEGRATOR_CURRENT = 190, - AXIS1__CONTROLLER__CURRENT_SETPOINT = 191, - AXIS1__CONTROLLER__CONFIG__CONTROL_MODE = 192, - AXIS1__CONTROLLER__CONFIG__POS_GAIN = 193, - AXIS1__CONTROLLER__CONFIG__VEL_GAIN = 194, - AXIS1__CONTROLLER__CONFIG__VEL_INTEGRATOR_GAIN = 195, - AXIS1__CONTROLLER__CONFIG__VEL_LIMIT = 196, - AXIS1__CONTROLLER__START_ANTICOGGING_CALIBRATION = 206, - AXIS1__ENCODER__ERROR = 207, - AXIS1__ENCODER__IS_READY = 208, - AXIS1__ENCODER__INDEX_FOUND = 209, - AXIS1__ENCODER__SHADOW_COUNT = 210, - AXIS1__ENCODER__COUNT_IN_CPR = 211, - AXIS1__ENCODER__OFFSET = 212, - AXIS1__ENCODER__INTERPOLATION = 213, - AXIS1__ENCODER__PHASE = 214, - AXIS1__ENCODER__POS_ESTIMATE = 215, - AXIS1__ENCODER__POS_CPR = 216, - AXIS1__ENCODER__HALL_STATE = 217, - AXIS1__ENCODER__PLL_VEL = 218, - AXIS1__ENCODER__PLL_KP = 219, - AXIS1__ENCODER__PLL_KI = 220, - AXIS1__ENCODER__CONFIG__MODE = 221, - AXIS1__ENCODER__CONFIG__USE_INDEX = 222, - AXIS1__ENCODER__CONFIG__PRE_CALIBRATED = 223, - AXIS1__ENCODER__CONFIG__IDX_SEARCH_SPEED = 224, - AXIS1__ENCODER__CONFIG__CPR = 225, - AXIS1__ENCODER__CONFIG__OFFSET = 226, - AXIS1__ENCODER__CONFIG__OFFSET_FLOAT = 227, - AXIS1__ENCODER__CONFIG__CALIB_RANGE = 228, - AXIS1__SENSORLESS_ESTIMATOR__ERROR = 229, - AXIS1__SENSORLESS_ESTIMATOR__PHASE = 230, - AXIS1__SENSORLESS_ESTIMATOR__PLL_POS = 231, - AXIS1__SENSORLESS_ESTIMATOR__PLL_VEL = 232, - AXIS1__SENSORLESS_ESTIMATOR__PLL_KP = 233, - AXIS1__SENSORLESS_ESTIMATOR__PLL_KI = 234, - TEST_PROPERTY = 235, - SAVE_CONFIGURATION = 242, - ERASE_CONFIGURATION = 243, - REBOOT = 244, - ENTER_DFU_MODE = 245, + VBUS_VOLTAGE = 1, + SERIAL_NUMBER = 2, + HW_VERSION_MAJOR = 3, + HW_VERSION_MINOR = 4, + HW_VERSION_VARIANT = 5, + FW_VERSION_MAJOR = 6, + FW_VERSION_MINOR = 7, + FW_VERSION_REVISION = 8, + FW_VERSION_UNRELEASED = 9, + USER_CONFIG_LOADED = 10, + BRAKE_RESISTOR_ARMED = 11, + SYSTEM_STATS__UPTIME = 12, + SYSTEM_STATS__MIN_HEAP_SPACE = 13, + SYSTEM_STATS__MIN_STACK_SPACE_AXIS0 = 14, + SYSTEM_STATS__MIN_STACK_SPACE_AXIS1 = 15, + SYSTEM_STATS__MIN_STACK_SPACE_COMMS = 16, + SYSTEM_STATS__MIN_STACK_SPACE_USB = 17, + SYSTEM_STATS__MIN_STACK_SPACE_UART = 18, + SYSTEM_STATS__MIN_STACK_SPACE_USB_IRQ = 19, + SYSTEM_STATS__MIN_STACK_SPACE_STARTUP = 20, + SYSTEM_STATS__USB__RX_CNT = 21, + SYSTEM_STATS__USB__TX_CNT = 22, + SYSTEM_STATS__USB__TX_OVERRUN_CNT = 23, + SYSTEM_STATS__I2C__ADDR = 24, + SYSTEM_STATS__I2C__ADDR_MATCH_CNT = 25, + SYSTEM_STATS__I2C__RX_CNT = 26, + SYSTEM_STATS__I2C__ERROR_CNT = 27, + CONFIG__BRAKE_RESISTANCE = 28, + CONFIG__ENABLE_UART = 29, + CONFIG__ENABLE_I2C_INSTEAD_OF_CAN = 30, + CONFIG__DC_BUS_UNDERVOLTAGE_TRIP_LEVEL = 31, + CONFIG__DC_BUS_OVERVOLTAGE_TRIP_LEVEL = 32, + TEST_PROPERTY = 235, + SAVE_CONFIGURATION = 242, + ERASE_CONFIGURATION = 243, + REBOOT = 244, + ENTER_DFU_MODE = 245, + + // Per-Axis endpoints (to be used with read_axis_property and write_axis_property) + AXIS__ERROR = 33, + AXIS__ENABLE_STEP_DIR = 34, + AXIS__CURRENT_STATE = 35, + AXIS__REQUESTED_STATE = 36, + AXIS__LOOP_COUNTER = 37, + AXIS__CONFIG__STARTUP_MOTOR_CALIBRATION = 38, + AXIS__CONFIG__STARTUP_ENCODER_INDEX_SEARCH = 39, + AXIS__CONFIG__STARTUP_ENCODER_OFFSET_CALIBRATION = 40, + AXIS__CONFIG__STARTUP_CLOSED_LOOP_CONTROL = 41, + AXIS__CONFIG__STARTUP_SENSORLESS_CONTROL = 42, + AXIS__CONFIG__ENABLE_STEP_DIR = 43, + AXIS__CONFIG__COUNTS_PER_STEP = 44, + AXIS__CONFIG__RAMP_UP_TIME = 45, + AXIS__CONFIG__RAMP_UP_DISTANCE = 46, + AXIS__CONFIG__SPIN_UP_CURRENT = 47, + AXIS__CONFIG__SPIN_UP_ACCELERATION = 48, + AXIS__CONFIG__SPIN_UP_TARGET_VEL = 49, + AXIS__MOTOR__ERROR = 50, + AXIS__MOTOR__ARMED_STATE = 51, + AXIS__MOTOR__IS_CALIBRATED = 52, + AXIS__MOTOR__CURRENT_MEAS_PHB = 53, + AXIS__MOTOR__CURRENT_MEAS_PHC = 54, + AXIS__MOTOR__DC_CALIB_PHB = 55, + AXIS__MOTOR__DC_CALIB_PHC = 56, + AXIS__MOTOR__PHASE_CURRENT_REV_GAIN = 57, + AXIS__MOTOR__CURRENT_CONTROL__P_GAIN = 58, + AXIS__MOTOR__CURRENT_CONTROL__I_GAIN = 59, + AXIS__MOTOR__CURRENT_CONTROL__V_CURRENT_CONTROL_INTEGRAL_D = 60, + AXIS__MOTOR__CURRENT_CONTROL__V_CURRENT_CONTROL_INTEGRAL_Q = 61, + AXIS__MOTOR__CURRENT_CONTROL__IBUS = 62, + AXIS__MOTOR__CURRENT_CONTROL__FINAL_V_ALPHA = 63, + AXIS__MOTOR__CURRENT_CONTROL__FINAL_V_BETA = 64, + AXIS__MOTOR__CURRENT_CONTROL__IQ_SETPOINT = 65, + AXIS__MOTOR__CURRENT_CONTROL__IQ_MEASURED = 66, + AXIS__MOTOR__CURRENT_CONTROL__MAX_ALLOWED_CURRENT = 67, + AXIS__MOTOR__GATE_DRIVER__DRV_FAULT = 68, + AXIS__MOTOR__TIMING_LOG__TIMING_LOG_GENERAL = 69, + AXIS__MOTOR__TIMING_LOG__TIMING_LOG_ADC_CB_I = 70, + AXIS__MOTOR__TIMING_LOG__TIMING_LOG_ADC_CB_DC = 71, + AXIS__MOTOR__TIMING_LOG__TIMING_LOG_MEAS_R = 72, + AXIS__MOTOR__TIMING_LOG__TIMING_LOG_MEAS_L = 73, + AXIS__MOTOR__TIMING_LOG__TIMING_LOG_ENC_CALIB = 74, + AXIS__MOTOR__TIMING_LOG__TIMING_LOG_IDX_SEARCH = 75, + AXIS__MOTOR__TIMING_LOG__TIMING_LOG_FOC_VOLTAGE = 76, + AXIS__MOTOR__TIMING_LOG__TIMING_LOG_FOC_CURRENT = 77, + AXIS__MOTOR__CONFIG__PRE_CALIBRATED = 78, + AXIS__MOTOR__CONFIG__POLE_PAIRS = 79, + AXIS__MOTOR__CONFIG__CALIBRATION_CURRENT = 80, + AXIS__MOTOR__CONFIG__RESISTANCE_CALIB_MAX_VOLTAGE = 81, + AXIS__MOTOR__CONFIG__PHASE_INDUCTANCE = 82, + AXIS__MOTOR__CONFIG__PHASE_RESISTANCE = 83, + AXIS__MOTOR__CONFIG__DIRECTION = 84, + AXIS__MOTOR__CONFIG__MOTOR_TYPE = 85, + AXIS__MOTOR__CONFIG__CURRENT_LIM = 86, + AXIS__CONTROLLER__POS_SETPOINT = 87, + AXIS__CONTROLLER__VEL_SETPOINT = 88, + AXIS__CONTROLLER__VEL_INTEGRATOR_CURRENT = 89, + AXIS__CONTROLLER__CURRENT_SETPOINT = 90, + AXIS__CONTROLLER__CONFIG__CONTROL_MODE = 91, + AXIS__CONTROLLER__CONFIG__POS_GAIN = 92, + AXIS__CONTROLLER__CONFIG__VEL_GAIN = 93, + AXIS__CONTROLLER__CONFIG__VEL_INTEGRATOR_GAIN = 94, + AXIS__CONTROLLER__CONFIG__VEL_LIMIT = 95, + AXIS__CONTROLLER__START_ANTICOGGING_CALIBRATION = 105, + AXIS__ENCODER__ERROR = 106, + AXIS__ENCODER__IS_READY = 107, + AXIS__ENCODER__INDEX_FOUND = 108, + AXIS__ENCODER__SHADOW_COUNT = 109, + AXIS__ENCODER__COUNT_IN_CPR = 110, + AXIS__ENCODER__OFFSET = 111, + AXIS__ENCODER__INTERPOLATION = 112, + AXIS__ENCODER__PHASE = 113, + AXIS__ENCODER__POS_ESTIMATE = 114, + AXIS__ENCODER__POS_CPR = 115, + AXIS__ENCODER__HALL_STATE = 116, + AXIS__ENCODER__PLL_VEL = 117, + AXIS__ENCODER__PLL_KP = 118, + AXIS__ENCODER__PLL_KI = 119, + AXIS__ENCODER__CONFIG__MODE = 120, + AXIS__ENCODER__CONFIG__USE_INDEX = 121, + AXIS__ENCODER__CONFIG__PRE_CALIBRATED = 122, + AXIS__ENCODER__CONFIG__IDX_SEARCH_SPEED = 123, + AXIS__ENCODER__CONFIG__CPR = 124, + AXIS__ENCODER__CONFIG__OFFSET = 125, + AXIS__ENCODER__CONFIG__OFFSET_FLOAT = 126, + AXIS__ENCODER__CONFIG__CALIB_RANGE = 127, + AXIS__SENSORLESS_ESTIMATOR__ERROR = 128, + AXIS__SENSORLESS_ESTIMATOR__PHASE = 129, + AXIS__SENSORLESS_ESTIMATOR__PLL_POS = 130, + AXIS__SENSORLESS_ESTIMATOR__PLL_VEL = 131, + AXIS__SENSORLESS_ESTIMATOR__PLL_KP = 132, + AXIS__SENSORLESS_ESTIMATOR__PLL_KI = 133, }; template @@ -274,6 +185,14 @@ template<> struct endpoint_type { typedef bool type; }; template<> struct endpoint_type { typedef bool type; }; template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef float type; }; +template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef void type; }; +template<> struct endpoint_type { typedef void type; }; +template<> struct endpoint_type { typedef void type; }; +template<> struct endpoint_type { typedef void type; }; + + +// Per-axis endpoints template<> struct endpoint_type { typedef uint16_t type; }; template<> struct endpoint_type { typedef bool type; }; template<> struct endpoint_type { typedef uint8_t type; }; @@ -366,103 +285,6 @@ template<> struct endpoint_type { typedef f template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef uint8_t type; }; -template<> struct endpoint_type { typedef uint8_t type; }; -template<> struct endpoint_type { typedef uint32_t type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint8_t type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef uint16_t type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef int32_t type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef int32_t type; }; -template<> struct endpoint_type { typedef uint8_t type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef uint8_t type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef void type; }; -template<> struct endpoint_type { typedef uint8_t type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef int32_t type; }; -template<> struct endpoint_type { typedef int32_t type; }; -template<> struct endpoint_type { typedef int32_t type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef uint8_t type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef uint8_t type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef bool type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef int32_t type; }; -template<> struct endpoint_type { typedef int32_t type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef uint8_t type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef float type; }; -template<> struct endpoint_type { typedef uint32_t type; }; -template<> struct endpoint_type { typedef void type; }; -template<> struct endpoint_type { typedef void type; }; -template<> struct endpoint_type { typedef void type; }; -template<> struct endpoint_type { typedef void type; }; template diff --git a/tools/odrive/code_generator.py b/tools/odrive/code_generator.py index 45c01c22..c14d6bcb 100644 --- a/tools/odrive/code_generator.py +++ b/tools/odrive/code_generator.py @@ -3,10 +3,12 @@ import jinja2 import os import json -def get_flat_endpoint_list(json, prefix): +def get_flat_endpoint_list(json, prefix, id_offset): flat_list = [] for item in json: item = item.copy() + if 'id' in item: + item['id'] -= id_offset if 'type' in item: if item['type'] in {'int8', 'uint8', 'int16', 'uint16', 'int32', 'uint32', 'int64', 'uint64'}: item['type'] += '_t' @@ -25,14 +27,22 @@ def get_flat_endpoint_list(json, prefix): item['name'] = prefix + item['name'] flat_list.append(item) if 'members' in item: - flat_list = flat_list + get_flat_endpoint_list(item['members'], prefix + item['name'] + '.') + flat_list = flat_list + get_flat_endpoint_list(item['members'], prefix + item['name'] + '.', id_offset) return flat_list def generate_code(odrv, template_file, output_file): json_data = odrv._json_data json_crc = odrv._json_crc - endpoints = get_flat_endpoint_list(json_data, '') + axis0_json = [item for item in json_data if item['name'].startswith("axis0")][0] + axis1_json = [item for item in json_data if item['name'].startswith("axis1")][0] + json_data = [item for item in json_data if not item['name'].startswith("axis")] + endpoints = get_flat_endpoint_list(json_data, '', 0) + per_axis_offset = axis1_json['members'][0]['id'] - axis0_json['members'][0]['id'] + axis_endpoints = get_flat_endpoint_list(axis0_json['members'], 'axis.', 0) + axis_endpoints_copy = get_flat_endpoint_list(axis1_json['members'], 'axis.', per_axis_offset) + if axis_endpoints != axis_endpoints_copy: + raise Exception("axis0 and axis1 don't look exactly equal") env = jinja2.Environment( #loader = jinja2.FileSystemLoader("/Data/Projects/") @@ -43,11 +53,15 @@ def generate_code(odrv, template_file, output_file): # Expose helper functions to jinja template code #env.filters["delimit"] = camel_case_to_words + #import ipdb; ipdb.set_trace() + # Load and render template template = env.from_string(template_file.read()) output = template.render( json_crc=json_crc, endpoints=endpoints, + per_axis_offset=per_axis_offset, + axis_endpoints=axis_endpoints, output_name=os.path.basename(output_file.name) ) diff --git a/tools/odrive_header_template.h.in b/tools/odrive_header_template.h.in index c07e0174..8a7befd1 100644 --- a/tools/odrive_header_template.h.in +++ b/tools/odrive_header_template.h.in @@ -14,8 +14,15 @@ namespace odrive { static constexpr const uint16_t json_crc = 0x{{ "%0x" | format(json_crc) }}; +static constexpr const uint16_t per_axis_offset = {{ per_axis_offset }}; + enum { {% for endpoint in endpoints %} - {{enum_name(endpoint)}} = {{endpoint.id}}, + {{enum_name(endpoint)}} = {{endpoint.id}}, +{%- endfor %} + + // Per-Axis endpoints (to be used with read_axis_property and write_axis_property) +{%- for endpoint in axis_endpoints %} + {{enum_name(endpoint)}} = {{endpoint.id}}, {%- endfor %} }; @@ -26,6 +33,11 @@ struct endpoint_type; template<> struct endpoint_type<{{enum_name(endpoint)}}> { typedef {{endpoint.type}} type; }; {% endfor %} +// Per-axis endpoints +{% for endpoint in axis_endpoints -%} +template<> struct endpoint_type<{{enum_name(endpoint)}}> { typedef {{endpoint.type}} type; }; +{% endfor %} + template using endpoint_type_t = typename endpoint_type::type; From 4f30d9f99675fdffcf027c1f31eb0359306c8cc5 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 26 Mar 2018 18:42:42 -0700 Subject: [PATCH 167/215] enable DMA for ADC1 in CubeMX --- Firmware/Board/v3/Odrive.ioc | 14 +++++++++++++- Firmware/Board/v3/Src/adc.c | 24 ++++++++++++++++++++++++ Firmware/Board/v3/Src/dma.c | 4 ++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/Firmware/Board/v3/Odrive.ioc b/Firmware/Board/v3/Odrive.ioc index ce8e1e2f..9aca69e1 100644 --- a/Firmware/Board/v3/Odrive.ioc +++ b/Firmware/Board/v3/Odrive.ioc @@ -78,9 +78,20 @@ ADC3.ScanConvMode=DISABLE CAN1.CalculateTimeBit=1142 CAN1.CalculateTimeQuantum=380.95238095238096 CAN1.IPParameters=CalculateTimeQuantum,CalculateTimeBit +Dma.ADC1.2.Direction=DMA_PERIPH_TO_MEMORY +Dma.ADC1.2.FIFOMode=DMA_FIFOMODE_DISABLE +Dma.ADC1.2.Instance=DMA2_Stream0 +Dma.ADC1.2.MemDataAlignment=DMA_MDATAALIGN_HALFWORD +Dma.ADC1.2.MemInc=DMA_MINC_ENABLE +Dma.ADC1.2.Mode=DMA_CIRCULAR +Dma.ADC1.2.PeriphDataAlignment=DMA_PDATAALIGN_HALFWORD +Dma.ADC1.2.PeriphInc=DMA_PINC_DISABLE +Dma.ADC1.2.Priority=DMA_PRIORITY_LOW +Dma.ADC1.2.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode Dma.Request0=UART4_RX Dma.Request1=UART4_TX -Dma.RequestsNb=2 +Dma.Request2=ADC1 +Dma.RequestsNb=3 Dma.UART4_RX.0.Direction=DMA_PERIPH_TO_MEMORY Dma.UART4_RX.0.FIFOMode=DMA_FIFOMODE_DISABLE Dma.UART4_RX.0.Instance=DMA1_Stream2 @@ -198,6 +209,7 @@ NVIC.ADC_IRQn=true\:5\:0\:false\:false\:true\:true\:true NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:true NVIC.DMA1_Stream2_IRQn=true\:5\:0\:false\:false\:true\:true\:true NVIC.DMA1_Stream4_IRQn=true\:5\:0\:false\:false\:true\:true\:false +NVIC.DMA2_Stream0_IRQn=true\:5\:0\:false\:false\:false\:true\:false NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:true NVIC.EXTI2_IRQn=true\:0\:0\:false\:false\:false\:false\:true NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:true diff --git a/Firmware/Board/v3/Src/adc.c b/Firmware/Board/v3/Src/adc.c index 6d0db380..0af9cbba 100644 --- a/Firmware/Board/v3/Src/adc.c +++ b/Firmware/Board/v3/Src/adc.c @@ -51,6 +51,7 @@ #include "adc.h" #include "gpio.h" +#include "dma.h" /* USER CODE BEGIN 0 */ @@ -63,6 +64,7 @@ ADC_HandleTypeDef hadc1; ADC_HandleTypeDef hadc2; ADC_HandleTypeDef hadc3; +DMA_HandleTypeDef hdma_adc1; /* ADC1 init function */ void MX_ADC1_Init(void) @@ -255,6 +257,25 @@ void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle) GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + /* ADC1 DMA Init */ + /* ADC1 Init */ + hdma_adc1.Instance = DMA2_Stream0; + hdma_adc1.Init.Channel = DMA_CHANNEL_0; + hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY; + hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE; + hdma_adc1.Init.MemInc = DMA_MINC_ENABLE; + hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; + hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + hdma_adc1.Init.Mode = DMA_CIRCULAR; + hdma_adc1.Init.Priority = DMA_PRIORITY_LOW; + hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + if (HAL_DMA_Init(&hdma_adc1) != HAL_OK) + { + _Error_Handler(__FILE__, __LINE__); + } + + __HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1); + /* ADC1 interrupt Init */ HAL_NVIC_SetPriority(ADC_IRQn, 5, 0); HAL_NVIC_EnableIRQ(ADC_IRQn); @@ -354,6 +375,9 @@ void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle) HAL_GPIO_DeInit(GPIOA, M1_TEMP_Pin|AUX_I_Pin|VBUS_S_Pin); + /* ADC1 DMA DeInit */ + HAL_DMA_DeInit(adcHandle->DMA_Handle); + /* ADC1 interrupt Deinit */ /* USER CODE BEGIN ADC1:ADC_IRQn disable */ /** diff --git a/Firmware/Board/v3/Src/dma.c b/Firmware/Board/v3/Src/dma.c index a725a585..55d5e034 100644 --- a/Firmware/Board/v3/Src/dma.c +++ b/Firmware/Board/v3/Src/dma.c @@ -68,6 +68,7 @@ void MX_DMA_Init(void) { /* DMA controller clock enable */ __HAL_RCC_DMA1_CLK_ENABLE(); + __HAL_RCC_DMA2_CLK_ENABLE(); /* DMA interrupt init */ /* DMA1_Stream2_IRQn interrupt configuration */ @@ -76,6 +77,9 @@ void MX_DMA_Init(void) /* DMA1_Stream4_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 5, 0); HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn); + /* DMA2_Stream0_IRQn interrupt configuration */ + HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 5, 0); + HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn); } From b9268e961720c7dccf7a87248415dbfd68fac4e4 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 26 Mar 2018 19:02:45 -0700 Subject: [PATCH 168/215] disable IRQ for DMA2_Stream0 This DMA stream is used to read values from ADC1 while ADC1 cycles through it's sequence of input channels. No interrupts are required to make this work. --- Firmware/Board/v3/Src/dma.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Firmware/Board/v3/Src/dma.c b/Firmware/Board/v3/Src/dma.c index 55d5e034..3de873ad 100644 --- a/Firmware/Board/v3/Src/dma.c +++ b/Firmware/Board/v3/Src/dma.c @@ -78,8 +78,10 @@ void MX_DMA_Init(void) HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 5, 0); HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn); /* DMA2_Stream0_IRQn interrupt configuration */ - HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 5, 0); - HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn); + // Dear STM, no we _don't_ want to fire an interrupt for this DMA + // (it's not possible to deselect this in CubeMX) + //HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 5, 0); + //HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn); } From 59187ef1f29f88f2d53d5fa1a4d1946490f155a2 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 26 Mar 2018 19:05:29 -0700 Subject: [PATCH 169/215] add patch file for DMA IRQ --- .../0003-disable-IRQ-for-DMA2_Stream0.patch | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Firmware/Board/v3/0003-disable-IRQ-for-DMA2_Stream0.patch diff --git a/Firmware/Board/v3/0003-disable-IRQ-for-DMA2_Stream0.patch b/Firmware/Board/v3/0003-disable-IRQ-for-DMA2_Stream0.patch new file mode 100644 index 00000000..282317d1 --- /dev/null +++ b/Firmware/Board/v3/0003-disable-IRQ-for-DMA2_Stream0.patch @@ -0,0 +1,33 @@ +From ab5ca860b3729d76a9c43c485776147ab69d2342 Mon Sep 17 00:00:00 2001 +From: Samuel Sadok +Date: Mon, 26 Mar 2018 19:02:45 -0700 +Subject: [PATCH] disable IRQ for DMA2_Stream0 + +This DMA stream is used to read values from ADC1 +while ADC1 cycles through it's sequence of input +channels. No interrupts are required to make +this work. +--- + Firmware/Board/v3/Src/dma.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/Firmware/Board/v3/Src/dma.c b/Firmware/Board/v3/Src/dma.c +index 55d5e03..3de873a 100644 +--- a/Firmware/Board/v3/Src/dma.c ++++ b/Firmware/Board/v3/Src/dma.c +@@ -78,8 +78,10 @@ void MX_DMA_Init(void) + HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 5, 0); + HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn); + /* DMA2_Stream0_IRQn interrupt configuration */ +- HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 5, 0); +- HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn); ++ // Dear STM, no we _don't_ want to fire an interrupt for this DMA ++ // (it's not possible to deselect this in CubeMX) ++ //HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 5, 0); ++ //HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn); + + } + +-- +2.16.2 + From b064800116ea03aacaa4e94b9caf29177cc434b4 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 27 Apr 2018 17:46:11 -0700 Subject: [PATCH 170/215] Add general purpose ADC API ADC1 is configured to sample channels 0 to 15 continuously at about 30kHz. Users can now set their GPIO of choice to analog mode (as long as it's wired to one of the analog channels) and then read the voltage at any time. The values can be read like this: GPIO_set_to_analog(GPIO_3_GPIO_Port, GPIO_3_Pin); my_voltage = get_adc_voltage(GPIO_3_GPIO_Port, GPIO_3_Pin); --- Firmware/Board/v3/Inc/gpio.h | 2 +- Firmware/Board/v3/Src/gpio.c | 11 +++ Firmware/CHANGELOG.md | 1 + Firmware/MotorControl/low_level.cpp | 106 +++++++++++++++++++++++ Firmware/MotorControl/low_level.h | 3 + Firmware/MotorControl/main.cpp | 3 + Firmware/MotorControl/odrive_main.h | 2 + Firmware/communication/communication.cpp | 4 + 8 files changed, 131 insertions(+), 1 deletion(-) diff --git a/Firmware/Board/v3/Inc/gpio.h b/Firmware/Board/v3/Inc/gpio.h index f8ffe61b..6ec71035 100644 --- a/Firmware/Board/v3/Inc/gpio.h +++ b/Firmware/Board/v3/Inc/gpio.h @@ -71,11 +71,11 @@ void MX_GPIO_Init(void); /* USER CODE BEGIN Prototypes */ void SetGPIO12toUART(); -void SetupENCIndexGPIO(); bool GPIO_subscribe(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin, uint32_t pull_up_down, void (*callback)(void*), void* ctx); void GPIO_unsubscribe(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin); +void GPIO_set_to_analog(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin); /* USER CODE END Prototypes */ diff --git a/Firmware/Board/v3/Src/gpio.c b/Firmware/Board/v3/Src/gpio.c index 9585749f..b3941ff3 100644 --- a/Firmware/Board/v3/Src/gpio.c +++ b/Firmware/Board/v3/Src/gpio.c @@ -260,6 +260,17 @@ void GPIO_unsubscribe(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin) { HAL_NVIC_DisableIRQ(get_irq_number(GPIO_pin)); } +// @brief Configures the specified GPIO as an analog input. +// This disables any subscriptions that were active for this pin. +void GPIO_set_to_analog(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin) { + GPIO_InitTypeDef GPIO_InitStruct; + GPIO_unsubscribe(GPIO_port, GPIO_pin); + GPIO_InitStruct.Pin = GPIO_pin; + GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(GPIO_port, &GPIO_InitStruct); +} + //Dispatch processing of external interrupts based on source void HAL_GPIO_EXTI_Callback(uint16_t GPIO_pin) { for (size_t i = 0; i < n_subscriptions; ++i) { diff --git a/Firmware/CHANGELOG.md b/Firmware/CHANGELOG.md index b675c703..59188818 100644 --- a/Firmware/CHANGELOG.md +++ b/Firmware/CHANGELOG.md @@ -35,6 +35,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * **USB Bootloader** * `make erase_config` to erase the configuration with an STLink (the configuration can also be erased from within explore_odrive.py, using `odrv0.erase_configuration()`) * Travis-CI builds firmware for all board versions and deploys the binaries when a tag is pushed to master +* General purpose ADC API. See function get_adc_voltage() in low_level.cpp for more detais. ### Changed * Most of the code from `lowlevel.c` moved to `axis.cpp`, `encoder.cpp`, `controller.cpp`, `sensorless_estimator.cpp`, `motor.cpp` and the corresponding header files diff --git a/Firmware/MotorControl/low_level.cpp b/Firmware/MotorControl/low_level.cpp index 41d58990..19b0500f 100644 --- a/Firmware/MotorControl/low_level.cpp +++ b/Firmware/MotorControl/low_level.cpp @@ -312,6 +312,112 @@ void low_level_fault(Motor::Error_t error) { safety_critical_disarm_brake_resistor(); } +// @brief ADC1 measurements are written to this buffer by DMA +uint16_t adc_measurements_[ADC_CHANNEL_COUNT] = { 0 }; + +// @brief Starts the general purpose ADC on the ADC1 peripheral. +// The measured ADC voltages can be read with get_adc_voltage(). +// +// ADC1 is set up to continuously sample all channels 0 to 15 in a +// round-robin fashion. +// DMA is used to copy the measured 12-bit values to adc_measurements_. +// +// The injected (high priority) channel of ADC1 is used to sample vbus_voltage. +// This conversion is triggered by TIM1 at the frequency of the motor control loop. +void start_general_purpose_adc() { + ADC_ChannelConfTypeDef sConfig; + + // Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) + hadc1.Instance = ADC1; + hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; + hadc1.Init.Resolution = ADC_RESOLUTION_12B; + hadc1.Init.ScanConvMode = ENABLE; + hadc1.Init.ContinuousConvMode = ENABLE; + hadc1.Init.DiscontinuousConvMode = DISABLE; + hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; + hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; + hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; + hadc1.Init.NbrOfConversion = ADC_CHANNEL_COUNT; + hadc1.Init.DMAContinuousRequests = ENABLE; + hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; + if (HAL_ADC_Init(&hadc1) != HAL_OK) + { + _Error_Handler((char*)__FILE__, __LINE__); + } + + // Set up sampling sequence (channel 0 ... channel 15) + sConfig.SamplingTime = ADC_SAMPLETIME_15CYCLES; + for (uint32_t channel = 0; channel < ADC_CHANNEL_COUNT; ++channel) { + sConfig.Channel = channel << ADC_CR1_AWDCH_Pos; + sConfig.Rank = channel + 1; // rank numbering starts at 1 + if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) + _Error_Handler((char*)__FILE__, __LINE__); + } + + HAL_ADC_Start_DMA(&hadc1, reinterpret_cast(adc_measurements_), ADC_CHANNEL_COUNT); +} + +// @brief Returns the ADC voltage associated with the specified pin. +// GPIO_set_to_analog() must be called first to put the Pin into +// analog mode. +// Returns NaN if the pin has no associated ADC1 channel. +// +// On ODrive 3.3 and 3.4 the following pins can be used with this function: +// GPIO_1, GPIO_2, GPIO_3, GPIO_4 and some pins that are connected to +// on-board sensors (M0_TEMP, M1_TEMP, AUX_TEMP) +// +// The ADC values are sampled in background at ~30kHz without +// any CPU involvement. +// +// Details: each of the 16 conversion takes (15+26) ADC clock +// cycles and the ADC, so the update rate of the entire sequence is: +// 21000kHz / (15+26) / 16 = 32kHz +// The true frequency is slightly lower because of the injected vbus +// measurements +float get_adc_voltage(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin) { + uint32_t channel = UINT32_MAX; + if (GPIO_port == GPIOA) { + if (GPIO_pin == GPIO_PIN_0) + channel = 0; + else if (GPIO_pin == GPIO_PIN_1) + channel = 1; + else if (GPIO_pin == GPIO_PIN_2) + channel = 2; + else if (GPIO_pin == GPIO_PIN_3) + channel = 3; + else if (GPIO_pin == GPIO_PIN_4) + channel = 4; + else if (GPIO_pin == GPIO_PIN_5) + channel = 5; + else if (GPIO_pin == GPIO_PIN_6) + channel = 6; + else if (GPIO_pin == GPIO_PIN_7) + channel = 7; + } else if (GPIO_port == GPIOB) { + if (GPIO_pin == GPIO_PIN_0) + channel = 8; + else if (GPIO_pin == GPIO_PIN_1) + channel = 9; + } else if (GPIO_port == GPIOC) { + if (GPIO_pin == GPIO_PIN_0) + channel = 10; + else if (GPIO_pin == GPIO_PIN_1) + channel = 11; + else if (GPIO_pin == GPIO_PIN_2) + channel = 12; + else if (GPIO_pin == GPIO_PIN_3) + channel = 13; + else if (GPIO_pin == GPIO_PIN_4) + channel = 14; + else if (GPIO_pin == GPIO_PIN_5) + channel = 15; + } + if (channel < ADC_CHANNEL_COUNT) + return ((float)adc_measurements_[channel]) * (3.3f / (float)(1 << 12)); + else + return 0.0f / 0.0f; // NaN +} + //-------------------------------- // IRQ Callbacks //-------------------------------- diff --git a/Firmware/MotorControl/low_level.h b/Firmware/MotorControl/low_level.h index e3784788..d99c9916 100644 --- a/Firmware/MotorControl/low_level.h +++ b/Firmware/MotorControl/low_level.h @@ -39,6 +39,9 @@ void start_adc_pwm(); void start_pwm(TIM_HandleTypeDef* htim); void sync_timers(TIM_HandleTypeDef* htim_a, TIM_HandleTypeDef* htim_b, uint16_t TIM_CLOCKSOURCE_ITRx, uint16_t count_offset); +void start_general_purpose_adc(); + +float get_adc_voltage(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin); void update_brake_current(); diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index b7ec03d5..217e9c2b 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -81,6 +81,9 @@ int odrive_main(void) { *encoder, *sensorless_estimator, *controller, *motor); } + // Start ADC for temperature measurements and user measurements + start_general_purpose_adc(); + // TODO: make dynamically reconfigurable #if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 if (board_config.enable_uart) { diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index a66fcb74..6e72270f 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -34,6 +34,8 @@ extern bool user_config_loaded; extern uint64_t serial_number; extern char serial_number_str[13]; +#define ADC_CHANNEL_COUNT 16 +extern uint16_t adc_measurements_[ADC_CHANNEL_COUNT]; #ifdef __cplusplus } diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 27e08bfb..322404ed 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -113,6 +113,10 @@ static inline auto make_obj_tree() { ), make_protocol_object("axis0", axes[0]->make_protocol_definitions()), make_protocol_object("axis1", axes[1]->make_protocol_definitions()), +#if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR == 4 + make_protocol_property("adc_gpio1", &adc_measurements_[0]), + make_protocol_property("adc_gpio2", &adc_measurements_[1]), +#endif make_protocol_function("save_configuration", static_functions, &StaticFunctions::save_configuration_helper), make_protocol_function("erase_configuration", static_functions, &StaticFunctions::erase_configuration_helper), make_protocol_function("reboot", static_functions, &StaticFunctions::NVIC_SystemReset_helper), From f7fa729c38d831109bb63e26ebb8d60e24563b79 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 27 Apr 2018 18:21:39 -0700 Subject: [PATCH 171/215] make ADC work --- .../Board/v3/Src/prev_board_ver/adc_V3_2.c | 20 +++++++++++++++++++ .../Board/v3/Src/prev_board_ver/adc_V3_4.c | 20 +++++++++++++++++++ Firmware/MotorControl/axis.hpp | 2 +- Firmware/MotorControl/board_config_v3.h | 12 +++++------ Firmware/MotorControl/main.cpp | 9 +++++++++ Firmware/MotorControl/odrive_main.h | 2 +- 6 files changed, 57 insertions(+), 8 deletions(-) diff --git a/Firmware/Board/v3/Src/prev_board_ver/adc_V3_2.c b/Firmware/Board/v3/Src/prev_board_ver/adc_V3_2.c index 2496df7f..bc2ddddf 100644 --- a/Firmware/Board/v3/Src/prev_board_ver/adc_V3_2.c +++ b/Firmware/Board/v3/Src/prev_board_ver/adc_V3_2.c @@ -2,6 +2,7 @@ ADC_HandleTypeDef hadc1; ADC_HandleTypeDef hadc2; ADC_HandleTypeDef hadc3; +DMA_HandleTypeDef hdma_adc1; /* ADC1 init function */ void MX_ADC1_Init(void) @@ -195,6 +196,25 @@ void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle) GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + /* ADC1 DMA Init */ + /* ADC1 Init */ + hdma_adc1.Instance = DMA2_Stream0; + hdma_adc1.Init.Channel = DMA_CHANNEL_0; + hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY; + hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE; + hdma_adc1.Init.MemInc = DMA_MINC_ENABLE; + hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; + hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + hdma_adc1.Init.Mode = DMA_CIRCULAR; + hdma_adc1.Init.Priority = DMA_PRIORITY_LOW; + hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + if (HAL_DMA_Init(&hdma_adc1) != HAL_OK) + { + _Error_Handler(__FILE__, __LINE__); + } + + __HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1); + /* ADC1 interrupt Init */ HAL_NVIC_SetPriority(ADC_IRQn, 5, 0); HAL_NVIC_EnableIRQ(ADC_IRQn); diff --git a/Firmware/Board/v3/Src/prev_board_ver/adc_V3_4.c b/Firmware/Board/v3/Src/prev_board_ver/adc_V3_4.c index 49862c97..31ce77d0 100644 --- a/Firmware/Board/v3/Src/prev_board_ver/adc_V3_4.c +++ b/Firmware/Board/v3/Src/prev_board_ver/adc_V3_4.c @@ -2,6 +2,7 @@ ADC_HandleTypeDef hadc1; ADC_HandleTypeDef hadc2; ADC_HandleTypeDef hadc3; +DMA_HandleTypeDef hdma_adc1; /* ADC1 init function */ void MX_ADC1_Init(void) @@ -194,6 +195,25 @@ void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle) GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + /* ADC1 DMA Init */ + /* ADC1 Init */ + hdma_adc1.Instance = DMA2_Stream0; + hdma_adc1.Init.Channel = DMA_CHANNEL_0; + hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY; + hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE; + hdma_adc1.Init.MemInc = DMA_MINC_ENABLE; + hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; + hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + hdma_adc1.Init.Mode = DMA_CIRCULAR; + hdma_adc1.Init.Priority = DMA_PRIORITY_LOW; + hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + if (HAL_DMA_Init(&hdma_adc1) != HAL_OK) + { + _Error_Handler(__FILE__, __LINE__); + } + + __HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1); + /* ADC1 interrupt Init */ HAL_NVIC_SetPriority(ADC_IRQn, 5, 0); HAL_NVIC_EnableIRQ(ADC_IRQn); diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index e5d10fa4..c39ee5f7 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -26,7 +26,7 @@ struct AxisConfig_t { bool startup_encoder_offset_calibration = false; // Date: Fri, 27 Apr 2018 18:22:38 -0700 Subject: [PATCH 172/215] update endpoint header --- ArduinoI2C/odrive_endpoints.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ArduinoI2C/odrive_endpoints.h b/ArduinoI2C/odrive_endpoints.h index 765da575..f82c2b6a 100644 --- a/ArduinoI2C/odrive_endpoints.h +++ b/ArduinoI2C/odrive_endpoints.h @@ -12,7 +12,7 @@ namespace odrive { -static constexpr const uint16_t json_crc = 0x64cd; +static constexpr const uint16_t json_crc = 0xbe97; static constexpr const uint16_t per_axis_offset = 101; @@ -50,10 +50,12 @@ enum { CONFIG__DC_BUS_UNDERVOLTAGE_TRIP_LEVEL = 31, CONFIG__DC_BUS_OVERVOLTAGE_TRIP_LEVEL = 32, TEST_PROPERTY = 235, - SAVE_CONFIGURATION = 242, - ERASE_CONFIGURATION = 243, - REBOOT = 244, - ENTER_DFU_MODE = 245, + ADC_GPIO1 = 242, + ADC_GPIO2 = 243, + SAVE_CONFIGURATION = 244, + ERASE_CONFIGURATION = 245, + REBOOT = 246, + ENTER_DFU_MODE = 247, // Per-Axis endpoints (to be used with read_axis_property and write_axis_property) AXIS__ERROR = 33, @@ -186,6 +188,8 @@ template<> struct endpoint_type { typedef boo template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef float type; }; template<> struct endpoint_type { typedef uint32_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; +template<> struct endpoint_type { typedef uint16_t type; }; template<> struct endpoint_type { typedef void type; }; template<> struct endpoint_type { typedef void type; }; template<> struct endpoint_type { typedef void type; }; From a94096b64da6cf466235c1729f44b449d1541db9 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 10 May 2018 06:56:13 -0700 Subject: [PATCH 173/215] Make subcomponent errors to always set relevant axis error; use do_checks and set_error --- Firmware/.vscode/c_cpp_properties.json | 6 +++-- Firmware/MotorControl/axis.cpp | 34 +++++++++++++++++--------- Firmware/MotorControl/axis.hpp | 20 +++------------ Firmware/MotorControl/controller.cpp | 2 +- Firmware/MotorControl/encoder.cpp | 27 +++++++++++++------- Firmware/MotorControl/encoder.hpp | 2 ++ Firmware/MotorControl/motor.cpp | 19 ++++++++------ Firmware/MotorControl/motor.hpp | 5 ++-- Firmware/README.md | 2 +- tools/odrive/enums.py | 2 +- tools/odrive/tests.py | 2 +- 11 files changed, 68 insertions(+), 53 deletions(-) diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index eab99cd5..7f70edfe 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -126,8 +126,10 @@ ], "limitSymbolsToIncludedHeaders": true, "databaseFilename": "" - } + }, + "cStandard": "c11", + "cppStandard": "c++17" } ], - "version": 3 + "version": 4 } \ No newline at end of file diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 7ddce582..c1576a82 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -92,16 +92,26 @@ void Axis::set_step_dir_enabled(bool enable) { } } -// @brief Returns true if everything is ok. -// Sets error and returns false otherwise. +// @brief Do axis level checks and call subcomponent do_checks +// Returns true if everything is ok. bool Axis::do_checks() { - if (!motor_.do_checks()) - return error_ |= ERROR_MOTOR_FAILED, false; + if (!brake_resistor_armed) + error_ |= ERROR_BRAKE_RESISTOR_DISARMED; + if ((current_state_ != AXIS_STATE_IDLE) && (motor_.armed_state_ == Motor::ARMED_STATE_DISARMED)) + // motor got disarmed in something other than the idle loop + error_ |= ERROR_MOTOR_DISARMED; if (!(vbus_voltage >= board_config.dc_bus_undervoltage_trip_level)) - return error_ |= ERROR_DC_BUS_UNDER_VOLTAGE, false; + error_ |= ERROR_DC_BUS_UNDER_VOLTAGE; if (!(vbus_voltage <= board_config.dc_bus_overvoltage_trip_level)) - return error_ |= ERROR_DC_BUS_OVER_VOLTAGE, false; - return true; + error_ |= ERROR_DC_BUS_OVER_VOLTAGE; + + // Sub-components should use set_error which will propegate to this error_ + motor_.do_checks(); + encoder_.do_checks(); + // sensorless_estimator_.do_checks(); + // controller_.do_checks(); + + return error_ == ERROR_NONE; } bool Axis::run_sensorless_spin_up() { @@ -115,7 +125,7 @@ bool Axis::run_sensorless_spin_up() { return error_ |= ERROR_MOTOR_FAILED, false; return x < 1.0f; }); - if (error_ != ERROR_NO_ERROR) + if (error_ != ERROR_NONE) return false; // Late Spin-up: accelerate @@ -129,7 +139,7 @@ bool Axis::run_sensorless_spin_up() { return error_ |= ERROR_MOTOR_FAILED, false; return vel < config_.spin_up_target_vel; }); - return error_ == ERROR_NO_ERROR; + return error_ == ERROR_NONE; } // Note run_sensorless_control_loop and run_closed_loop_control_loop are very similar and differ only in where we get the estimate from. @@ -152,7 +162,7 @@ bool Axis::run_sensorless_control_loop() { return true; }); set_step_dir_enabled(false); - return error_ == ERROR_NO_ERROR; + return error_ == ERROR_NONE; } bool Axis::run_closed_loop_control_loop() { @@ -171,7 +181,7 @@ bool Axis::run_closed_loop_control_loop() { return true; }); set_step_dir_enabled(false); - return error_ == ERROR_NO_ERROR; + return error_ == ERROR_NONE; } bool Axis::run_idle_loop() { @@ -183,7 +193,7 @@ bool Axis::run_idle_loop() { encoder_.update(nullptr, nullptr, nullptr); return true; }); - return error_ == ERROR_NO_ERROR; + return error_ == ERROR_NONE; } // Infinite loop that does calibration and enters main control loop as appropriate diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index c39ee5f7..e558c388 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -42,7 +42,7 @@ struct AxisConfig_t { class Axis { public: enum Error_t { - ERROR_NO_ERROR = 0x00, + ERROR_NONE = 0x00, ERROR_INVALID_STATE = 0x01, // void run_control_loop(const T& update_handler) { while (requested_state_ == AXIS_STATE_UNDEFINED) { - if (!brake_resistor_armed) { - error_ |= ERROR_BRAKE_RESISTOR_DISARMED; - break; - } - if ((current_state_ != AXIS_STATE_IDLE) && (motor_.armed_state_ == Motor::ARMED_STATE_DISARMED)) { - // motor got disarmed in something other than the idle loop - error_ |= ERROR_MOTOR_DISARMED; - break; - } - if (motor_.error_ != Motor::ERROR_NO_ERROR) { - error_ |= ERROR_MOTOR_FAILED; - break; - } - - if (!do_checks()) // error set during function call + if (!do_checks()) // look for errors at axis level and also all subcomponents break; // Run main loop function, defer quitting for after wait @@ -160,7 +146,7 @@ public: volatile bool thread_id_valid_ = false; // variables exposed on protocol - Error_t error_ = ERROR_NO_ERROR; + Error_t error_ = ERROR_NONE; bool enable_step_dir_ = false; // auto enabled after calibration, based on config.enable_step_dir AxisState_t requested_state_ = AXIS_STATE_STARTUP_SEQUENCE; AxisState_t task_chain_[10] = { AXIS_STATE_UNDEFINED }; diff --git a/Firmware/MotorControl/controller.cpp b/Firmware/MotorControl/controller.cpp index 7cc55894..43bb206b 100644 --- a/Firmware/MotorControl/controller.cpp +++ b/Firmware/MotorControl/controller.cpp @@ -46,7 +46,7 @@ void Controller::set_current_setpoint(float current_setpoint) { void Controller::start_anticogging_calibration() { // Ensure the cogging map was correctly allocated earlier and that the motor is capable of calibrating - if (anticogging_.cogging_map != NULL && axis_->error_ == Axis::ERROR_NO_ERROR) { + if (anticogging_.cogging_map != NULL && axis_->error_ == Axis::ERROR_NONE) { anticogging_.calib_anticogging = true; } } diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 5bf57c1c..55de1bc8 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -31,6 +31,15 @@ void Encoder::setup() { enc_index_cb_wrapper, this); } +void Encoder::set_error(Encoder::Error_t error) { + error_ |= error; + axis_->error_ |= Axis::ERROR_MOTOR_FAILED; +} + +bool Encoder::do_checks(){ + return error_ == ERROR_NONE; +} + //-------------------- // Hardware Dependent //-------------------- @@ -112,7 +121,7 @@ bool Encoder::run_index_search() { // continue until the index is found return !index_found_; }); - return axis_->error_ != Axis::ERROR_NO_ERROR; + return axis_->error_ != Axis::ERROR_NONE; } // @brief Turns the motor in one direction for a bit and then in the other @@ -148,7 +157,7 @@ bool Encoder::run_offset_calibration() { axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB); return ++i < start_lock_duration * current_meas_hz; }); - if (axis_->error_ != Axis::ERROR_NO_ERROR) + if (axis_->error_ != Axis::ERROR_NONE) return false; int32_t init_enc_val = shadow_count_; @@ -170,7 +179,7 @@ bool Encoder::run_offset_calibration() { return ++i < num_steps; }); - if (axis_->error_ != Axis::ERROR_NO_ERROR) + if (axis_->error_ != Axis::ERROR_NONE) return false; //TODO avoid recomputing elec_rad_per_enc every time @@ -179,7 +188,7 @@ bool Encoder::run_offset_calibration() { float actual_encoder_delta_abs = fabsf(shadow_count_-init_enc_val); if(fabsf(actual_encoder_delta_abs - expected_encoder_delta)/expected_encoder_delta > config_.calib_range) { - error_ |= ERROR_CPR_OUT_OF_RANGE; + set_error(ERROR_CPR_OUT_OF_RANGE); return false; } // check direction @@ -191,7 +200,7 @@ bool Encoder::run_offset_calibration() { axis_->motor_.config_.direction = -1; } else { // Encoder response error - error_ |= ERROR_RESPONSE; + set_error(ERROR_RESPONSE); return false; } @@ -211,7 +220,7 @@ bool Encoder::run_offset_calibration() { return ++i < num_steps; }); - if (axis_->error_ != Axis::ERROR_NO_ERROR) + if (axis_->error_ != Axis::ERROR_NONE) return false; offset_ = encvaluesum / (num_steps * 2); @@ -238,7 +247,7 @@ static bool decode_hall(uint8_t hall_state, int32_t* hall_cnt) { bool Encoder::update(float* pos_estimate, float* vel_estimate, float* phase_output) { // Check that we don't get problems with discrete time approximation if (!(current_meas_period * pll_kp_ < 1.0f)) { - error_ |= ERROR_NUMERICAL; + set_error(ERROR_NUMERICAL); return false; } @@ -260,13 +269,13 @@ bool Encoder::update(float* pos_estimate, float* vel_estimate, float* phase_outp if (delta_enc > 3) delta_enc -= 6; } else { - error_ |= ERROR_ILLEGAL_HALL_STATE; + set_error(ERROR_ILLEGAL_HALL_STATE); return false; } } break; default: { - error_ |= ERROR_UNSUPPORTED_ENCODER_MODE; + set_error(ERROR_UNSUPPORTED_ENCODER_MODE); return 0; } break; } diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index fafd0cc5..fe3d717e 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -41,6 +41,8 @@ public: Config_t& config); void setup(); + void set_error(Error_t error); + bool do_checks(); void enc_index_cb(); diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index dad64bc5..2b0a9daa 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -125,9 +125,14 @@ bool Motor::check_DRV_fault() { return true; } +void Motor::set_error(Motor::Error_t error){ + error_ |= error; + axis_->error_ |= Axis::ERROR_MOTOR_FAILED; +} + bool Motor::do_checks() { if (!check_DRV_fault()) { - error_ |= ERROR_DRV_FAULT; + set_error(ERROR_DRV_FAULT); return false; } return true; @@ -172,7 +177,7 @@ bool Motor::measure_phase_resistance(float test_current, float max_voltage) { float Ialpha = -(current_meas_.phB + current_meas_.phC); test_voltage += (kI * current_meas_period) * (test_current - Ialpha); if (test_voltage > max_voltage || test_voltage < -max_voltage) - return error_ |= ERROR_PHASE_RESISTANCE_OUT_OF_RANGE, false; + return set_error(ERROR_PHASE_RESISTANCE_OUT_OF_RANGE), false; // Test voltage along phase A if (!enqueue_voltage_timings(test_voltage, 0.0f)) @@ -181,7 +186,7 @@ bool Motor::measure_phase_resistance(float test_current, float max_voltage) { return ++i < num_test_cycles; }); - if (axis_->error_ != Axis::ERROR_NO_ERROR) + if (axis_->error_ != Axis::ERROR_NONE) return false; //// De-energize motor @@ -212,7 +217,7 @@ bool Motor::measure_phase_inductance(float voltage_low, float voltage_high) { return ++t < (num_cycles << 1); }); - if (axis_->error_ != Axis::ERROR_NO_ERROR) + if (axis_->error_ != Axis::ERROR_NONE) return false; //// De-energize motor @@ -228,7 +233,7 @@ bool Motor::measure_phase_inductance(float voltage_low, float voltage_high) { config_.phase_inductance = L; // TODO arbitrary values set for now if (L < 1e-6f || L > 500e-6f) - return error_ |= ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE, false; + return set_error(ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE), false; return true; } @@ -255,7 +260,7 @@ bool Motor::run_calibration() { bool Motor::enqueue_modulation_timings(float mod_alpha, float mod_beta) { float tA, tB, tC; if (SVM(mod_alpha, mod_beta, &tA, &tB, &tC) != 0) - return error_ |= ERROR_NUMERICAL, false; + return set_error(ERROR_NUMERICAL), false; next_timings_[0] = (uint16_t)(tA * (float)TIM_1_8_PERIOD_CLOCKS); next_timings_[1] = (uint16_t)(tB * (float)TIM_1_8_PERIOD_CLOCKS); next_timings_[2] = (uint16_t)(tC * (float)TIM_1_8_PERIOD_CLOCKS); @@ -363,7 +368,7 @@ bool Motor::update(float current_setpoint, float phase) { if(!FOC_voltage(0.0f, current_setpoint, phase)) return false; } else { - error_ |= ERROR_NOT_IMPLEMENTED_MOTOR_TYPE; + set_error(ERROR_NOT_IMPLEMENTED_MOTOR_TYPE); return false; } return true; diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index b369460e..d33a44bb 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -54,7 +54,7 @@ typedef struct { class Motor { public: enum Error_t { - ERROR_NO_ERROR = 0, + ERROR_NONE = 0, ERROR_PHASE_RESISTANCE_OUT_OF_RANGE = 0x0001, ERROR_PHASE_INDUCTANCE_OUT_OF_RANGE = 0x0002, ERROR_ADC_FAILED = 0x0004, @@ -102,6 +102,7 @@ public: void update_current_controller_gains(); void DRV8301_setup(); bool check_DRV_fault(); + void set_error(Error_t error); bool do_checks(); void log_timing(TimingLog_t log_idx); float phase_current_from_adcval(uint32_t ADCValue); @@ -133,7 +134,7 @@ public: uint16_t timing_log_[TIMING_LOG_NUM_SLOTS] = { 0 }; // variables exposed on protocol - Error_t error_ = ERROR_NO_ERROR; + Error_t error_ = ERROR_NONE; // Do not write to this variable directly! // It is for exclusive use by the safety_critical_... functions. ArmedState_t armed_state_ = ARMED_STATE_DISARMED; diff --git a/Firmware/README.md b/Firmware/README.md index ad315ac0..d101dce3 100644 --- a/Firmware/README.md +++ b/Firmware/README.md @@ -272,7 +272,7 @@ If you have an encoder with an index (Z) signal, you may avoid having to do the

The error nummber corresponds to the following: -0. `ERROR_NO_ERROR` +0. `ERROR_NONE` 1. `ERROR_PHASE_RESISTANCE_TIMING` 2. `ERROR_PHASE_RESISTANCE_MEASUREMENT_TIMEOUT` 3. `ERROR_PHASE_RESISTANCE_OUT_OF_RANGE` diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 52b1e610..324a03a9 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -11,7 +11,7 @@ AXIS_STATE_ENCODER_INDEX_SEARCH = 6 AXIS_STATE_ENCODER_OFFSET_CALIBRATION = 7 AXIS_STATE_CLOSED_LOOP_CONTROL = 8 -AXIS_ERROR_NO_ERROR = 0 +AXIS_ERROR_NONE = 0 AXIS_ERROR_INVALID_STATE = 1 #AXIS_ERROR_DC_BUS_UNDER_VOLTAGE = 2 #AXIS_ERROR_DC_BUS_OVER_VOLTAGE = 3 diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index 43ef182b..6fe5df1b 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -111,7 +111,7 @@ def request_state(axis_ctx: AxisTestContext, state, expect_success=True): else: test_assert_eq(axis_ctx.handle.current_state, AXIS_STATE_IDLE) test_assert_eq(axis_ctx.handle.error, AXIS_ERROR_INVALID_STATE) - axis_ctx.handle.error = AXIS_ERROR_NO_ERROR # reset error + axis_ctx.handle.error = AXIS_ERROR_NONE # reset error def set_limits(axis_ctx: AxisTestContext, logger, vel_limit=20000, current_limit=10): """ From 0d07c71f95c1038b2e03d2b2e52fcdf214ad95af Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 10 May 2018 07:33:54 -0700 Subject: [PATCH 174/215] push estimator updates into control loop prefix --- Firmware/MotorControl/axis.cpp | 40 +++++++++---------- Firmware/MotorControl/axis.hpp | 3 ++ Firmware/MotorControl/encoder.cpp | 18 ++------- Firmware/MotorControl/encoder.hpp | 4 +- Firmware/MotorControl/motor.cpp | 6 +-- Firmware/MotorControl/motor.hpp | 2 +- .../MotorControl/sensorless_estimator.cpp | 20 +--------- .../MotorControl/sensorless_estimator.hpp | 4 +- 8 files changed, 33 insertions(+), 64 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index c1576a82..7729f5fe 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -114,6 +114,14 @@ bool Axis::do_checks() { return error_ == ERROR_NONE; } +// @brief Update all esitmators +bool Axis::do_updates() { + // Sub-components should use set_error which will propegate to this error_ + encoder_.update(); + sensorless_estimator_.update(); + return error_ == ERROR_NONE; +} + bool Axis::run_sensorless_spin_up() { // Early Spin-up: spiral up current float x = 0.0f; @@ -146,19 +154,15 @@ bool Axis::run_sensorless_spin_up() { bool Axis::run_sensorless_control_loop() { set_step_dir_enabled(config_.enable_step_dir); run_control_loop([this](){ - float pos_estimate, vel_estimate, phase, current_setpoint; - if (controller_.config_.control_mode >= CTRL_MODE_POSITION_CONTROL) return error_ |= ERROR_POS_CTRL_DURING_SENSORLESS, false; - // We update the encoder just in case someone needs the output for testing - encoder_.update(nullptr, nullptr, nullptr); - if (!sensorless_estimator_.update(&pos_estimate, &vel_estimate, &phase)) - return error_ |= ERROR_SENSORLESS_ESTIMATOR_FAILED, false; - if (!controller_.update(pos_estimate, vel_estimate, ¤t_setpoint)) + // Note that all estimators are updated in the loop prefix in run_control_loop + float current_setpoint; + if (!controller_.update(sensorless_estimator_.pll_pos_, sensorless_estimator_.pll_vel_, ¤t_setpoint)) return error_ |= ERROR_CONTROLLER_FAILED, false; - if (!motor_.update(current_setpoint, phase)) - return error_ |= ERROR_MOTOR_FAILED, false; + if (!motor_.update(current_setpoint, sensorless_estimator_.phase_)) + return false; // set_error should update axis.error_ return true; }); set_step_dir_enabled(false); @@ -168,16 +172,12 @@ bool Axis::run_sensorless_control_loop() { bool Axis::run_closed_loop_control_loop() { set_step_dir_enabled(config_.enable_step_dir); run_control_loop([this](){ - float pos_estimate, vel_estimate, phase, current_setpoint; - - // We update the sensorless estimator just in case someone needs the output for testing - sensorless_estimator_.update(nullptr, nullptr, nullptr); - if (!encoder_.update(&pos_estimate, &vel_estimate, &phase)) - return error_ |= ERROR_ENCODER_FAILED, false; - if (!controller_.update(pos_estimate, vel_estimate, ¤t_setpoint)) - return error_ |= ERROR_CONTROLLER_FAILED, false; - if (!motor_.update(current_setpoint, phase)) - return error_ |= ERROR_MOTOR_FAILED, false; + // Note that all estimators are updated in the loop prefix in run_control_loop + float current_setpoint; + if (!controller_.update(encoder_.pos_estimate_, encoder_.pll_vel_, ¤t_setpoint)) + return error_ |= ERROR_CONTROLLER_FAILED, false; //TODO: Make controller.set_error + if (!motor_.update(current_setpoint, encoder_.phase_)) + return false; // set_error should update axis.error_ return true; }); set_step_dir_enabled(false); @@ -189,8 +189,6 @@ bool Axis::run_idle_loop() { // if and only if we're in AXIS_STATE_IDLE safety_critical_disarm_motor_pwm(motor_); run_control_loop([this](){ - sensorless_estimator_.update(nullptr, nullptr, nullptr); - encoder_.update(nullptr, nullptr, nullptr); return true; }); return error_ == ERROR_NONE; diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index e558c388..8eda2892 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -78,6 +78,7 @@ public: bool check_DRV_fault(); bool check_PSU_brownout(); bool do_checks(); + bool do_updates(); // @brief Runs the specified update handler at the frequency of the current measurements. // @@ -104,6 +105,8 @@ public: while (requested_state_ == AXIS_STATE_UNDEFINED) { if (!do_checks()) // look for errors at axis level and also all subcomponents break; + if (!do_updates()) // Update all estimators + break; // Run main loop function, defer quitting for after wait // TODO: change arming logic to arm after waiting diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 55de1bc8..22ba7a9f 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -108,8 +108,6 @@ bool Encoder::run_index_search() { index_found_ = false; float phase = 0.0f; axis_->run_control_loop([&](){ - update(nullptr, nullptr, nullptr); - phase = wrap_pm_pi(phase + omega * current_meas_period); float v_alpha = voltage_magnitude * arm_cos_f32(phase); @@ -150,8 +148,6 @@ bool Encoder::run_offset_calibration() { // go to motor zero phase for start_lock_duration to get ready to scan int i = 0; axis_->run_control_loop([&](){ - update(nullptr, nullptr, nullptr); - if (!axis_->motor_.enqueue_voltage_timings(voltage_magnitude, 0.0f)) return false; // error set inside enqueue_voltage_timings axis_->motor_.log_timing(Motor::TIMING_LOG_ENC_CALIB); @@ -166,8 +162,6 @@ bool Encoder::run_offset_calibration() { // scan forward i = 0; axis_->run_control_loop([&](){ - update(nullptr, nullptr, nullptr); - float phase = wrap_pm_pi(scan_distance * (float)i / (float)num_steps - scan_distance / 2.0f); float v_alpha = voltage_magnitude * arm_cos_f32(phase); float v_beta = voltage_magnitude * arm_sin_f32(phase); @@ -207,8 +201,6 @@ bool Encoder::run_offset_calibration() { // scan backwards i = 0; axis_->run_control_loop([&](){ - update(nullptr, nullptr, nullptr); - float phase = wrap_pm_pi(-scan_distance * (float)i / (float)num_steps + scan_distance / 2.0f); float v_alpha = voltage_magnitude * arm_cos_f32(phase); float v_beta = voltage_magnitude * arm_sin_f32(phase); @@ -244,10 +236,10 @@ static bool decode_hall(uint8_t hall_state, int32_t* hall_cnt) { } } -bool Encoder::update(float* pos_estimate, float* vel_estimate, float* phase_output) { +bool Encoder::update() { // Check that we don't get problems with discrete time approximation if (!(current_meas_period * pll_kp_ < 1.0f)) { - set_error(ERROR_NUMERICAL); + set_error(ERROR_UNSTABLE_GAIN); return false; } @@ -276,7 +268,7 @@ bool Encoder::update(float* pos_estimate, float* vel_estimate, float* phase_outp default: { set_error(ERROR_UNSUPPORTED_ENCODER_MODE); - return 0; + return false; } break; } @@ -324,9 +316,5 @@ bool Encoder::update(float* pos_estimate, float* vel_estimate, float* phase_outp if (fabsf(pll_vel_) < 0.5f * current_meas_period * pll_ki_) pll_vel_ = 0.0f; //align delta-sigma on zero to prevent jitter - // Assign output arguments - if (pos_estimate) *pos_estimate = pos_estimate_; - if (vel_estimate) *vel_estimate = pll_vel_; - if (phase_output) *phase_output = phase_; return true; } diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index fe3d717e..34fc14d7 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -9,7 +9,7 @@ class Encoder { public: enum Error_t { ERROR_NONE = 0, - ERROR_NUMERICAL = 0x01, + ERROR_UNSTABLE_GAIN = 0x01, ERROR_CPR_OUT_OF_RANGE = 0x02, ERROR_RESPONSE = 0x04, ERROR_UNSUPPORTED_ENCODER_MODE = 0x08, @@ -53,7 +53,7 @@ public: bool run_index_search(); bool run_offset_calibration(); - bool update(float* pos_estimate, float* vel_estimate, float* phase); + bool update(); const EncoderHardwareConfig_t& hw_config_; Config_t& config_; diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 2b0a9daa..482bbf87 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -172,8 +172,6 @@ bool Motor::measure_phase_resistance(float test_current, float max_voltage) { size_t i = 0; axis_->run_control_loop([&](){ - axis_->encoder_.update(nullptr, nullptr, nullptr); - float Ialpha = -(current_meas_.phB + current_meas_.phC); test_voltage += (kI * current_meas_period) * (test_current - Ialpha); if (test_voltage > max_voltage || test_voltage < -max_voltage) @@ -205,8 +203,6 @@ bool Motor::measure_phase_inductance(float voltage_low, float voltage_high) { size_t t = 0; axis_->run_control_loop([&](){ - axis_->encoder_.update(nullptr, nullptr, nullptr); - int i = t & 1; Ialphas[i] += -current_meas_.phB - current_meas_.phC; @@ -260,7 +256,7 @@ bool Motor::run_calibration() { bool Motor::enqueue_modulation_timings(float mod_alpha, float mod_beta) { float tA, tB, tC; if (SVM(mod_alpha, mod_beta, &tA, &tB, &tC) != 0) - return set_error(ERROR_NUMERICAL), false; + return set_error(ERROR_MODULATION_MAGNITUDE), false; next_timings_[0] = (uint16_t)(tA * (float)TIM_1_8_PERIOD_CLOCKS); next_timings_[1] = (uint16_t)(tB * (float)TIM_1_8_PERIOD_CLOCKS); next_timings_[2] = (uint16_t)(tC * (float)TIM_1_8_PERIOD_CLOCKS); diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index d33a44bb..182817d0 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -62,7 +62,7 @@ public: ERROR_CONTROL_DEADLINE_MISSED = 0x0010, ERROR_NOT_IMPLEMENTED_MOTOR_TYPE = 0x0020, ERROR_BRAKE_CURRENT_OUT_OF_RANGE = 0x0040, - ERROR_NUMERICAL = 0x0080, + ERROR_MODULATION_MAGNITUDE = 0x0080, ERROR_BRAKE_DEADTIME_VIOLATION = 0x0100, ERROR_UNEXPECTED_TIMER_CALLBACK = 0x0200 }; diff --git a/Firmware/MotorControl/sensorless_estimator.cpp b/Firmware/MotorControl/sensorless_estimator.cpp index 1098b38c..96439157 100644 --- a/Firmware/MotorControl/sensorless_estimator.cpp +++ b/Firmware/MotorControl/sensorless_estimator.cpp @@ -12,7 +12,7 @@ SensorlessEstimator::SensorlessEstimator() pll_ki_ = 0.25f * (pll_kp_ * pll_kp_); } -bool SensorlessEstimator::update(float* pos_estimate, float* vel_estimate, float* phase_output) { +bool SensorlessEstimator::update() { // Algorithm based on paper: Sensorless Control of Surface-Mount Permanent-Magnet Synchronous Motors Based on a Nonlinear Observer // http://cas.ensmp.fr/~praly/Telechargement/Journaux/2010-IEEE_TPEL-Lee-Hong-Nam-Ortega-Praly-Astolfi.pdf // In particular, equation 8 (and by extension eqn 4 and 6). @@ -23,7 +23,7 @@ bool SensorlessEstimator::update(float* pos_estimate, float* vel_estimate, float // Check that we don't get problems with discrete time approximation if (!(current_meas_period * pll_kp_ < 1.0f)) { - error_ |= ERROR_NUMERICAL; + error_ |= ERROR_UNSTABLE_GAIN; return false; } @@ -83,21 +83,5 @@ bool SensorlessEstimator::update(float* pos_estimate, float* vel_estimate, float // update PLL velocity pll_vel_ += current_meas_period * pll_ki_ * delta_phase; - //TODO TEMP TEST HACK - // static int trigger_ctr = 0; - // if (++trigger_ctr >= 3*current_meas_hz) { - // trigger_ctr = 0; - - // //Change to sensorless units - // motor->vel_gain = 15.0f / 200.0f; - // motor->vel_setpoint = 800.0f * motor->encoder.motor_dir; - - // //Change mode - // motor->rotor_mode = ROTOR_MODE_SENSORLESS; - // } - - if (pos_estimate) *pos_estimate = pll_pos_; - if (vel_estimate) *vel_estimate = pll_vel_; - if (phase_output) *phase_output = phase_; return true; }; diff --git a/Firmware/MotorControl/sensorless_estimator.hpp b/Firmware/MotorControl/sensorless_estimator.hpp index 910bc05a..d8590d0e 100644 --- a/Firmware/MotorControl/sensorless_estimator.hpp +++ b/Firmware/MotorControl/sensorless_estimator.hpp @@ -5,12 +5,12 @@ class SensorlessEstimator { public: enum Error_t { ERROR_NONE = 0, - ERROR_NUMERICAL = 0x01, + ERROR_UNSTABLE_GAIN = 0x01, }; SensorlessEstimator(); - bool update(float* pos_estimate, float* vel_estimate, float* phase); + bool update(); Axis* axis_ = nullptr; // set by Axis constructor From 7188e2a1c2705f8c7b3637a92a50d8df2439fbc2 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 10 May 2018 14:42:35 -0700 Subject: [PATCH 175/215] clean state machine level error writing --- Firmware/MotorControl/axis.cpp | 59 ++++++++++++++----------------- Firmware/MotorControl/encoder.cpp | 2 +- 2 files changed, 27 insertions(+), 34 deletions(-) diff --git a/Firmware/MotorControl/axis.cpp b/Firmware/MotorControl/axis.cpp index 7729f5fe..3efd8f8d 100644 --- a/Firmware/MotorControl/axis.cpp +++ b/Firmware/MotorControl/axis.cpp @@ -38,6 +38,7 @@ void Axis::setup() { static void run_state_machine_loop_wrapper(void* ctx) { reinterpret_cast(ctx)->run_state_machine_loop(); + reinterpret_cast(ctx)->thread_id_valid_ = false; } // @brief Starts run_state_machine_loop in a new thread @@ -254,43 +255,37 @@ void Axis::run_state_machine_loop() { // Handlers should exit if requested_state != AXIS_STATE_UNDEFINED bool status; switch (current_state_) { - case AXIS_STATE_MOTOR_CALIBRATION: - status = motor_.run_calibration(); - if (!status) - error_ |= ERROR_MOTOR_FAILED; - break; + case AXIS_STATE_MOTOR_CALIBRATION: + status = motor_.run_calibration(); + break; - case AXIS_STATE_ENCODER_INDEX_SEARCH: - status = encoder_.run_index_search(); - if (!status) - error_ |= ERROR_ENCODER_FAILED; - break; + case AXIS_STATE_ENCODER_INDEX_SEARCH: + status = encoder_.run_index_search(); + break; - case AXIS_STATE_ENCODER_OFFSET_CALIBRATION: - status = encoder_.run_offset_calibration(); - if (!status) - error_ |= ERROR_ENCODER_FAILED; - break; + case AXIS_STATE_ENCODER_OFFSET_CALIBRATION: + status = encoder_.run_offset_calibration(); + break; - case AXIS_STATE_SENSORLESS_CONTROL: - status = run_sensorless_spin_up(); // TODO: restart if desired - if (status) - status = run_sensorless_control_loop(); - break; + case AXIS_STATE_SENSORLESS_CONTROL: + status = run_sensorless_spin_up(); // TODO: restart if desired + if (status) + status = run_sensorless_control_loop(); + break; - case AXIS_STATE_CLOSED_LOOP_CONTROL: - status = run_closed_loop_control_loop(); - break; + case AXIS_STATE_CLOSED_LOOP_CONTROL: + status = run_closed_loop_control_loop(); + break; - case AXIS_STATE_IDLE: - run_idle_loop(); - status = motor_.arm(); // done with idling - try to arm the motor - break; + case AXIS_STATE_IDLE: + run_idle_loop(); + status = motor_.arm(); // done with idling - try to arm the motor + break; - default: - error_ |= ERROR_INVALID_STATE; - status = false; // this will set the state to idle - break; + default: + error_ |= ERROR_INVALID_STATE; + status = false; // this will set the state to idle + break; } // If the state failed, go to idle, else advance task chain @@ -299,6 +294,4 @@ void Axis::run_state_machine_loop() { else memcpy(task_chain_, task_chain_ + 1, sizeof(task_chain_) - sizeof(task_chain_[0])); } - - thread_id_valid_ = false; } diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 22ba7a9f..b8428c57 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -119,7 +119,7 @@ bool Encoder::run_index_search() { // continue until the index is found return !index_found_; }); - return axis_->error_ != Axis::ERROR_NONE; + return true; } // @brief Turns the motor in one direction for a bit and then in the other From 03c7c1d43cab6abb28d20c8d3bf92bc8aea81ea7 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Thu, 10 May 2018 14:48:48 -0700 Subject: [PATCH 176/215] restore default config values --- Firmware/MotorControl/encoder.hpp | 2 +- Firmware/MotorControl/motor.hpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Firmware/MotorControl/encoder.hpp b/Firmware/MotorControl/encoder.hpp index 34fc14d7..60299dca 100644 --- a/Firmware/MotorControl/encoder.hpp +++ b/Firmware/MotorControl/encoder.hpp @@ -30,7 +30,7 @@ public: // In this case the encoder will enter ready // state as soon as the index is found. float idx_search_speed = 10.0f; // [rad/s electrical] - int32_t cpr = 72; //(2048 * 4); // Default resolution of CUI-AMT102 encoder, + int32_t cpr = (2048 * 4); // Default resolution of CUI-AMT102 encoder, int32_t offset = 0; // If pre_calibrated is true, this is copied into encoder.offset_ once // index search succeeds float offset_float = 0.0f; // Sub-count phase alignment offset diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 182817d0..961c05ce 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -37,9 +37,9 @@ typedef struct { // example: current_lim and calibration_current will instead determine the maximum voltage applied to the motor. typedef struct { bool pre_calibrated = false; // can be set to true to indicate that all values here are valid - int32_t pole_pairs = 12; - float calibration_current = 6.0f; // [A] - float resistance_calib_max_voltage = 2.0f; // [V] - You may need to increase this if this voltage isn't sufficient to drive calibration_current through the motor. + int32_t pole_pairs = 7; + float calibration_current = 10.0f; // [A] + float resistance_calib_max_voltage = 1.0f; // [V] - You may need to increase this if this voltage isn't sufficient to drive calibration_current through the motor. float phase_inductance = 0.0f; // to be set by measure_phase_inductance float phase_resistance = 0.0f; // to be set by measure_phase_resistance int32_t direction = 1; // 1 or -1 @@ -48,7 +48,7 @@ typedef struct { // Read out max_allowed_current to see max supported value for current_lim. // You can change DRV8301_ShuntAmpGain to get a different range. // float current_lim = 75.0f; //[A] - float current_lim = 6.0f; //[A] + float current_lim = 10.0f; //[A] } MotorConfig_t; class Motor { From fd803a19f19f52918683f63e37b06fc0e0502462 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Fri, 11 May 2018 23:22:48 -0700 Subject: [PATCH 177/215] implement automatic firmware download --- tools/odrive/dfu.py | 452 +++++++++++++++++++------------- tools/odrive/dfuse/DfuDevice.py | 119 +++++++++ tools/odrive/utils.py | 19 ++ tools/odrive/version.py | 26 +- tools/odrivetool | 11 +- 5 files changed, 434 insertions(+), 193 deletions(-) diff --git a/tools/odrive/dfu.py b/tools/odrive/dfu.py index 1efd4592..3e63887a 100755 --- a/tools/odrive/dfu.py +++ b/tools/odrive/dfu.py @@ -9,8 +9,9 @@ import time import threading import platform import struct -import array -import fractions +import requests +import re +import io import usb.core import odrive.discovery from odrive.utils import Event @@ -24,46 +25,17 @@ except: sys.exit(1) -SIZE_MULTIPLIERS = {' ': 1, 'K': 1024, 'M' : 1024*1024} -MAX_TRANSFER_SIZE = 2048 +def get_fw_version_string(fw_version): + if (fw_version[0], fw_version[1], fw_version[2]) == (0, 0, 0): + return "[unknown version]" + else: + return "v{}.{}.{}{}".format(fw_version[0], fw_version[1], fw_version[2], "-dev" if fw_version[3] else "") - -def get_device_sectors(dfudev): - """ - Returns a list of all sectors on the device. - Each sector is represented as a dictionary with the following keys: - - name: name of the associated memory region (e.g. "Internal Flash") - - alt: USB alternate setting associated with this memory region - - addr: Start address of the sector (e.g. 0x08004000 for the second flash sectors) - - baseaddr: Start address of the memory region associated with the sector - (e.g. 0x08000000 for all flash sectors) - - len: Number of bytes in the sector - """ - for name, alt in dfudev.alternates(): - # example for name: - # '@Internal Flash /0x08000000/04*016Kg,01*064Kg,07*128Kg' - label, baseaddr, layout = name.split('/') - baseaddr = int(baseaddr, 0) # convert hex to decimal - addr = baseaddr - - for sector in layout.split(','): - repeat, size = map(int, sector[:-2].split('*')) - size *= SIZE_MULTIPLIERS[sector[-2].upper()] - mode = sector[-1] - - while repeat > 0: - # TODO: verify if the section is writable - yield { - 'name': label.strip().strip('@'), - 'alt': alt, - 'baseaddr': baseaddr, - 'addr': addr, - 'len': size, - 'mode': mode - } - - addr += size - repeat -= 1 +def get_hw_version_string(hw_version): + if hw_version == (0, 0, 0): + return "[unknown version]" + else: + return "v{}.{}{}".format(hw_version[0], hw_version[1], ("-" + str(hw_version[2]) + "V") if hw_version[2] > 0 else "") def populate_sectors(sectors, hexfile): """ @@ -88,66 +60,6 @@ def populate_sectors(sectors, hexfile): # TODO: verify if the section is writable yield (sector, hexfile.tobinarray(addr, addr + size - 1)) -def set_alternate_safe(dfudev, alt): - dfudev.set_alternate(alt) - if dfudev.get_state() == DfuState.DFU_ERROR: - dfudev.clear_status() - dfudev.wait_while_state(DfuState.DFU_ERROR) - -#def clear_error(dfudev) -def set_address_safe(dfudev, addr): - dfudev.set_address(addr) - status = dfudev.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY) - if status[1] != DfuState.DFU_DOWNLOAD_IDLE: - raise RuntimeError("An error occured. Device Status: %r" % status) - # take device out of DFU_DOWNLOAD_SYNC and into DFU_IDLE - dfudev.abort() - status = dfudev.wait_while_state(DfuState.DFU_DOWNLOAD_SYNC) - if status[1] != DfuState.DFU_IDLE: - raise RuntimeError("An error occured. Device Status: %r" % status) - - -def erase(dfudev, sector): - set_alternate_safe(dfudev, sector['alt']) - dfudev.erase(sector['addr']) - status = dfudev.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY, timeout=sector['len']/32) - if status[1] != DfuState.DFU_DOWNLOAD_IDLE: - raise RuntimeError("An error occured. Device Status: %r" % status) - -def flash(dfudev, sector, data): - set_alternate_safe(dfudev, sector['alt']) - set_address_safe(dfudev, sector['addr']) - - transfer_size = fractions.gcd(sector['len'], MAX_TRANSFER_SIZE) - - blocks = [data[i:i + transfer_size] for i in range(0, len(data), transfer_size)] - for blocknum, block in enumerate(blocks): - #print('write to {:08X} ({} bytes)'.format( - # sector['addr'] + blocknum * TRANSFER_SIZE, len(block))) - dfudev.write(blocknum, block) - status = dfudev.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY) - if status[1] != DfuState.DFU_DOWNLOAD_IDLE: - raise RuntimeError("An error occured. Device Status: %r" % status) - -def read(dfudev, sector): - """ - Reads data from the specified sector - Returns: a byte array containing the data - """ - set_alternate_safe(dfudev, sector['alt']) - set_address_safe(dfudev, sector['addr']) - - transfer_size = fractions.gcd(sector['len'], MAX_TRANSFER_SIZE) - #blocknum_offset = int((sector['addr'] - sector['baseaddr']) / transfer_size) - - - data = array.array(u'B') - for blocknum in range(int(sector['len'] / transfer_size)): - #print('read at {:08X}'.format(sector['addr'] + blocknum * TRANSFER_SIZE)) - deviceBlock = dfudev.read(blocknum, transfer_size) - data.extend(deviceBlock) - dfudev.abort() # take device into DFU_IDLE - return data def get_first_mismatch_index(array1, array2): """ @@ -161,42 +73,133 @@ def get_first_mismatch_index(array1, array2): return pos return None - -def jump_to_application(dfudev, address): - set_address_safe(dfudev, address) - #dfudev.set_address(address) - #status = dfudev.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY) - #if status[1] != DfuState.DFU_DOWNLOAD_IDLE: - # raise RuntimeError("An error occured. Device Status: {}".format(status[1])) - - dfudev.leave() - status = dfudev.wait_while_state(DfuState.DFU_MANIFEST_SYNC) - if status[1] != DfuState.DFU_MANIFEST: - raise RuntimeError("An error occured. Device Status: {}".format(status[1])) - - -def dump_otp(): +def dump_otp(dfudev): """ Dumps the contents of the one-time-programmable memory. The OTP will be used in future versions of this script to determine the board version. """ # 512 Byte OTP - otp_sector = [s for s in sectors if s['name'] == 'OTP Memory' and s['addr'] == 0x1fff7800][0] - data = read(dfudev, otp_sector) + otp_sector = [s for s in dfudev.sectors if s['name'] == 'OTP Memory' and s['addr'] == 0x1fff7800][0] + data = dfudev.read_sector(otp_sector) print(' '.join('{:02X}'.format(x) for x in data)) # 16 lock bytes - otp_lock_sector = [s for s in sectors if s['name'] == 'OTP Memory' and s['addr'] == 0x1fff7A00][0] - data = read(dfudev, otp_lock_sector) + otp_lock_sector = [s for s in dfudev.sectors if s['name'] == 'OTP Memory' and s['addr'] == 0x1fff7A00][0] + data = dfudev.read_sector(otp_lock_sector) print(' '.join('{:02X}'.format(x) for x in data)) +class Firmware(): + def __init__(self): + self.fw_version = (0, 0, 0, True) + self.hw_version = (0, 0, 0) + + @staticmethod + def is_newer(a, b): + return (a[0] > b[0] or + (a[0] == b[0] and + (a[1] > b[1] or + (a[1] == b[1] and + (a[2] > b[2] or + (a[2] == b[2] and + (not a[3] and a[3]))))))) + + def __gt__(self, other): + """ + Compares two firmware versions. If both versions are equal, the + prerelease version is considered older than the release version. + """ + if not isinstance(other, tuple): + other = other.fw_version + return Firmware.is_newer(self.fw_version, other) + + def __lt__(self, other): + """ + Compares two firmware versions. If both versions are equal, the + prerelease version is considered older than the release version. + """ + if not isinstance(other, tuple): + other = other.fw_version + return Firmware.is_newer(other, self.fw_version) + + def is_compatible(self, hw_version): + """ + Determines if this firmware is compatible + with the specified hardware version + """ + return self.hw_version == hw_version + +class FirmwareFromGithub(Firmware): + """ + Represents a firmware asset + """ + def __init__(self, release_json, asset_json): + Firmware.__init__(self) + if release_json['draft'] or release_json['prerelease']: + release_json['tag_name'] += "*" + self.fw_version = odrive.version.version_str_to_tuple(release_json['tag_name']) + + hw_version_regex = r'.*v([0-9]+).([0-9]+)(-(?P[0-9]+)V)?.hex' + hw_version_match = re.search(hw_version_regex, asset_json['name']) + self.hw_version = (int(hw_version_match[1]), + int(hw_version_match[2]), + int(hw_version_match.groupdict().get('voltage') or 0)) + self.github_asset_id = asset_json['id'] + self.hex = None + # no technical reason to fetch this - just interesting + self.download_count = asset_json['download_count'] + + def get_as_hex(self): + """ + Returns the content of the firmware in as a binary array in Intel Hex format + """ + if self.hex is None: + response = requests.get('https://api.github.com/repos/madcowswe/ODrive/releases/assets/' + str(self.github_asset_id), + headers={'Accept': 'application/octet-stream'}) + if response.status_code != 200: + raise Exception("failed to download firmware") + self.hex = response.content + return io.StringIO(self.hex.decode('utf-8')) + +class FirmwareFromFile(Firmware): + def __init__(self, file): + Firmware.__init__(self) + self._file = file + def get_as_hex(self): + return self._file + +def get_all_github_firmwares(): + response = requests.get('https://api.github.com/repos/madcowswe/ODrive/releases') + if response.status_code != 200: + raise Exception("could not fetch releases") + response_json = response.json() + + for release_json in response_json: + for asset_json in release_json['assets']: + try: + if asset_json['name'].lower().endswith('.hex'): + fw = FirmwareFromGithub(release_json, asset_json) + yield fw + except Exception as ex: + print(ex) + +def get_newest_firmware(hw_version): + """ + Returns the newest available firmware for the specified hardware version + """ + firmwares = get_all_github_firmwares() + firmwares = filter(lambda fw: not fw.fw_version[3], firmwares) # ignore prereleases + firmwares = filter(lambda fw: fw.hw_version == hw_version, firmwares) + firmwares = list(firmwares) + firmwares.sort() + return firmwares[-1] if len(firmwares) else None + def show_deferred_message(message, cancellation_token): """ Shows a message after 10s, unless cancellation_token gets set. """ def show_message_thread(message, cancellation_token): - for i in range(1,10): + for _ in range(1,10): if cancellation_token.is_set(): return time.sleep(1) @@ -206,104 +209,152 @@ def show_deferred_message(message, cancellation_token): t.daemon = True t.start() -def put_odrive_into_dfu_mode(my_drive, cancellation_token): +def put_into_dfu_mode(device, cancellation_token): """ Puts the specified device into DFU mode """ - if not hasattr(my_drive, "enter_dfu_mode"): + if not hasattr(device, "enter_dfu_mode"): print("The firmware on device {} does not support DFU. You need to \n" "flash the firmware once using STLink (`make flash`), after that \n" "DFU with this script should work fine." - .format(my_drive.__channel__.usb_device.serial_number)) + .format(device.__channel__.usb_device.serial_number)) return - hw_version_major = my_drive.hw_version_major if hasattr(my_drive, 'hw_version_major') else 3 - hw_version_minor = my_drive.hw_version_minor if hasattr(my_drive, 'hw_version_minor') else 4 - if hw_version_major == 3 and hw_version_minor >= 5: - print("Putting device {} into DFU mode...".format(my_drive.__channel__.usb_device.serial_number)) - try: - my_drive.enter_dfu_mode() - except odrive.protocol.ChannelBrokenException: - pass # this is expected because the device reboots - if platform.system() == "Windows": - show_deferred_message("Still waiting for the device to reappear.\n" - "Use the Zadig utility to set the driver of 'STM32 BOOTLOADER' to libusb-win32.", - cancellation_token) - else: - print("Found device {}".format(my_drive.__channel__.usb_device.serial_number)) + hw_version_major = device.hw_version_major if hasattr(device, 'hw_version_major') else 3 + hw_version_minor = device.hw_version_minor if hasattr(device, 'hw_version_minor') else 4 + if hw_version_major == 3 and hw_version_minor < 5: print(" DFU mode is not supported on board version 3.4 or earlier.") print(" This is because entering DFU mode on such a device would") print(" break the brake resistor FETs under some circumstances.") + raise Exception("not supported") + + print("Putting device {} into DFU mode...".format(device.__channel__.usb_device.serial_number)) + try: + device.enter_dfu_mode() + except odrive.protocol.ChannelBrokenException: + pass # this is expected because the device reboots + if platform.system() == "Windows": + show_deferred_message("Still waiting for the device to reappear.\n" + "Use the Zadig utility to set the driver of 'STM32 BOOTLOADER' to libusb-win32.", + cancellation_token) -def launch_dfu(args, app_shutdown_token): +def find_device_in_dfu_mode(serial_number, cancellation_token): """ - Waits for a device that matches args.path and args.serial_number - and then upgrades the device's firmware. + Polls libusb until a device in DFU mode is found """ + while not cancellation_token.is_set(): + params = {} if serial_number == None else {'serial_number': serial_number} + stm_device = usb.core.find(idVendor=0x0483, idProduct=0xdf11, **params) + if stm_device != None: + return stm_device + time.sleep(1) + return None + +def update_device(device, firmware, verbose, cancellation_token): + """ + Updates the specified device with the specified firmware. + The device passed to this function can either be in + normal mode or in DFU mode. + The firmware should be an instance of Firmware or None. + If firmware is None, the newest firmware for the device is + downloaded from GitHub releases. + """ + + if isinstance(device, usb.core.Device): + serial_number = device.serial_number + dfudev = DfuDevice(device) + if (verbose): + print("OTP:") + dump_otp(dfudev) + + # Read hardware version from one-time-programmable memory + otp_sector = [s for s in dfudev.sectors if s['name'] == 'OTP Memory' and s['addr'] == 0x1fff7800][0] + otp_data = dfudev.read_sector(otp_sector) + if otp_data[0] == 0: + otp_data = otp_data[16:] + if otp_data[0] == 0xfe: + hw_version = (otp_data[3], otp_data[4], otp_data[5]) + else: + hw_version = (0, 0, 0) + else: + serial_number = device.__channel__.usb_device.serial_number + dfudev = None + + # Read hardware version as reported from firmware + hw_version_major = device.hw_version_major if hasattr(device, 'hw_version_major') else 0 + hw_version_minor = device.hw_version_minor if hasattr(device, 'hw_version_minor') else 0 + hw_version_variant = device.hw_version_variant if hasattr(device, 'hw_version_variant') else 0 + hw_version = (hw_version_major, hw_version_minor, hw_version_variant) + + fw_version_major = device.fw_version_major if hasattr(device, 'fw_version_major') else 0 + fw_version_minor = device.fw_version_minor if hasattr(device, 'fw_version_minor') else 0 + fw_version_revision = device.fw_version_revision if hasattr(device, 'fw_version_revision') else 0 + fw_version_prerelease = device.fw_version_prerelease if hasattr(device, 'fw_version_prerelease') else True + fw_version = (fw_version_major, fw_version_minor, fw_version_revision, fw_version_prerelease) + + print("Found ODrive {} ({}) with firmware {}{}".format( + serial_number, + get_hw_version_string(hw_version), + get_fw_version_string(fw_version), + " in DFU mode" if dfudev is not None else "")) + + if firmware is None: + if hw_version == (0, 0, 0): + if dfudev is None: + suggestion = 'You have to manually flash an up-to-date firmware to make automatic checks work. Run `odrivetool dfu --help` for more info.' + else: + suggestion = 'Run "make write_otp" to program the board version.' + raise Exception('Cannot check online for new firmware because the board version is unknown. ' + suggestion) + print("Checking online for newest firmware...") + firmware = get_newest_firmware(hw_version) + if firmware is None: + raise Exception("could not find any firmware release for this board version") + + print("Updating to firmware {}".format(get_fw_version_string(firmware.fw_version))) + if firmware < fw_version: + print("Warning: you are about to flash firmware {} which is older than the firmware on the device ({}).".format( + get_fw_version_string(firmware.fw_version), + get_fw_version_string(fw_version))) + if not odrive.utils.yes_no_prompt("Do you want to flash this firmware anyway?", True): + return # load hex file # TODO: Either use the elf format or pack a custom format with a manifest. # This way we can for instance verify the target board version and only - # have to publish one file for every board. - hexfile = IntelHex(args.file) + # have to publish one file for every board (instead of elf AND hex files). + hexfile = IntelHex(firmware.get_as_hex()) - if (args.verbose): + if (verbose): print("Contiguous segments in hex file:") for start, end in hexfile.segments(): print(" {:08X} to {:08X}".format(start, end - 1)) - serial_number = args.serial_number + # Put the device into DFU mode if it's not already in DFU mode + if dfudev is None: + put_into_dfu_mode(device, cancellation_token) + stm_device = find_device_in_dfu_mode(serial_number, cancellation_token) + dfudev = DfuDevice(stm_device) - find_odrive_cancellation_token = Event(app_shutdown_token) - - print("Waiting for ODrive...") - - # Scan for ODrives not in DFU mode and put them into DFU mode once they appear - # We only scan on USB because DFU is only possible over USB - odrive.discovery.find_all(args.path, serial_number, - lambda dev: put_odrive_into_dfu_mode(dev, find_odrive_cancellation_token), - find_odrive_cancellation_token, app_shutdown_token) - - # Poll libUSB until a device in DFU mode is found - while not app_shutdown_token.is_set(): - params = {} if serial_number == None else {'serial_number': serial_number} - stm_device = usb.core.find(idVendor=0x0483, idProduct=0xdf11, **params) - if stm_device != None: - break - time.sleep(1) - find_odrive_cancellation_token.set() # we don't need this thread anymore - if app_shutdown_token.is_set(): - sys.exit(1) - print("Found device {} in DFU mode".format(stm_device.serial_number)) - - dfudev = DfuDevice(stm_device) - - sectors = list(get_device_sectors(dfudev)) - - if (args.verbose): + if (verbose): print("Sectors on device: ") - for sector in sectors: + for sector in dfudev.sectors: print(" {:08X} to {:08X} ({})".format( sector['addr'], sector['addr'] + sector['len'] - 1, sector['name'])) # fill sectors with data - touched_sectors = list(populate_sectors(sectors, hexfile)) + touched_sectors = list(populate_sectors(dfudev.sectors, hexfile)) - if (args.verbose): + if (verbose): print("The following sectors will be flashed: ") for sector,_ in touched_sectors: print(" {:08X} to {:08X}".format(sector['addr'], sector['addr'] + sector['len'] - 1)) - if (args.verbose): - print("OTP:") - dump_otp() - # Erase try: for i, (sector, data) in enumerate(touched_sectors): print("Erasing... (sector {}/{}) \r".format(i, len(touched_sectors)), end='', flush=True) - erase(dfudev, sector) + dfudev.erase_sector(sector) print('Erasing... done \r', end='', flush=True) finally: print('', flush=True) @@ -312,7 +363,7 @@ def launch_dfu(args, app_shutdown_token): try: for i, (sector, data) in enumerate(touched_sectors): print("Flashing... (sector {}/{}) \r".format(i, len(touched_sectors)), end='', flush=True) - flash(dfudev, sector, data) + dfudev.write_sector(sector, data) print('Flashing... done \r', end='', flush=True) finally: print('', flush=True) @@ -321,7 +372,7 @@ def launch_dfu(args, app_shutdown_token): try: for i, (sector, expected_data) in enumerate(touched_sectors): print("Verifying... (sector {}/{}) \r".format(i, len(touched_sectors)), end='', flush=True) - observed_data = read(dfudev, sector) + observed_data = dfudev.read_sector(sector) mismatch_pos = get_first_mismatch_index(observed_data, expected_data) if not mismatch_pos is None: mismatch_pos -= mismatch_pos % 16 @@ -341,7 +392,42 @@ def launch_dfu(args, app_shutdown_token): # So for debugging you should comment this last part out. # Jump to application - jump_to_application(dfudev, 0x08000000) + dfudev.jump_to_application(0x08000000) + +def launch_dfu(args, app_shutdown_token): + """ + Waits for a device that matches args.path and args.serial_number + and then upgrades the device's firmware. + """ + + serial_number = args.serial_number + find_odrive_cancellation_token = Event(app_shutdown_token) + + print("Waiting for ODrive...") + + devices = [None, None] + + # Start background thread to scan for ODrives in DFU mode + def find_device_in_dfu_mode_thread(): + devices[0] = find_device_in_dfu_mode(serial_number, find_odrive_cancellation_token) + find_odrive_cancellation_token.set() + threading.Thread(target=find_device_in_dfu_mode_thread).start() + + # Scan for ODrives not in DFU mode + # We only scan on USB because DFU is only implemented over USB + devices[1] = odrive.discovery.find_any("usb", serial_number, + find_odrive_cancellation_token, app_shutdown_token) + find_odrive_cancellation_token.set() + + device = devices[0] or devices[1] + firmware = FirmwareFromFile(args.file) if args.file else None + + try: + update_device(device, firmware, args.verbose, app_shutdown_token) + except Exception as ex: + if args.verbose: + raise + print(ex) diff --git a/tools/odrive/dfuse/DfuDevice.py b/tools/odrive/dfuse/DfuDevice.py index b9ca449c..e7158d3b 100644 --- a/tools/odrive/dfuse/DfuDevice.py +++ b/tools/odrive/dfuse/DfuDevice.py @@ -1,5 +1,8 @@ import usb.util import time +import fractions +import array +from odrive.dfuse.DfuState import DfuState DFU_REQUEST_SEND = 0x21 DFU_REQUEST_RECEIVE = 0xa1 @@ -12,6 +15,9 @@ DFU_CLRSTATUS = 0x04 DFU_GETSTATE = 0x05 DFU_ABORT = 0x06 +SIZE_MULTIPLIERS = {' ': 1, 'K': 1024, 'M' : 1024*1024} +MAX_TRANSFER_SIZE = 2048 + # Order is LSB first def address_to_4bytes(a): return [ a % 256, (a >> 8)%256, (a >> 16)%256, (a >> 24)%256 ] @@ -24,6 +30,7 @@ class DfuDevice: self.intf = None #self.dev.reset() self.cfg.set() + self.sectors = list(self.get_device_sectors()) def alternates(self): return [(usb.util.get_string(self.dev, intf.iInterface), intf) for intf in self.cfg] @@ -98,3 +105,115 @@ class DfuDevice: return status + ## High level functions ## + # by ODrive Robotics + + def get_device_sectors(self): + """ + Returns a list of all sectors on the device. + Each sector is represented as a dictionary with the following keys: + - name: name of the associated memory region (e.g. "Internal Flash") + - alt: USB alternate setting associated with this memory region + - addr: Start address of the sector (e.g. 0x08004000 for the second flash sectors) + - baseaddr: Start address of the memory region associated with the sector + (e.g. 0x08000000 for all flash sectors) + - len: Number of bytes in the sector + """ + for name, alt in self.alternates(): + # example for name: + # '@Internal Flash /0x08000000/04*016Kg,01*064Kg,07*128Kg' + label, baseaddr, layout = name.split('/') + baseaddr = int(baseaddr, 0) # convert hex to decimal + addr = baseaddr + + for sector in layout.split(','): + repeat, size = map(int, sector[:-2].split('*')) + size *= SIZE_MULTIPLIERS[sector[-2].upper()] + mode = sector[-1] + + while repeat > 0: + # TODO: verify if the section is writable + yield { + 'name': label.strip().strip('@'), + 'alt': alt, + 'baseaddr': baseaddr, + 'addr': addr, + 'len': size, + 'mode': mode + } + + addr += size + repeat -= 1 + + def set_alternate_safe(self, alt): + self.set_alternate(alt) + if self.get_state() == DfuState.DFU_ERROR: + self.clear_status() + self.wait_while_state(DfuState.DFU_ERROR) + + #def clear_error(self) + def set_address_safe(self, addr): + self.set_address(addr) + status = self.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY) + if status[1] != DfuState.DFU_DOWNLOAD_IDLE: + raise RuntimeError("An error occured. Device Status: %r" % status) + # take device out of DFU_DOWNLOAD_SYNC and into DFU_IDLE + self.abort() + status = self.wait_while_state(DfuState.DFU_DOWNLOAD_SYNC) + if status[1] != DfuState.DFU_IDLE: + raise RuntimeError("An error occured. Device Status: %r" % status) + + + def erase_sector(self, sector): + self.set_alternate_safe(sector['alt']) + self.erase(sector['addr']) + status = self.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY, timeout=sector['len']/32) + if status[1] != DfuState.DFU_DOWNLOAD_IDLE: + raise RuntimeError("An error occured. Device Status: %r" % status) + + def write_sector(self, sector, data): + self.set_alternate_safe(sector['alt']) + self.set_address_safe(sector['addr']) + + transfer_size = fractions.gcd(sector['len'], MAX_TRANSFER_SIZE) + + blocks = [data[i:i + transfer_size] for i in range(0, len(data), transfer_size)] + for blocknum, block in enumerate(blocks): + #print('write to {:08X} ({} bytes)'.format( + # sector['addr'] + blocknum * TRANSFER_SIZE, len(block))) + self.write(blocknum, block) + status = self.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY) + if status[1] != DfuState.DFU_DOWNLOAD_IDLE: + raise RuntimeError("An error occured. Device Status: %r" % status) + + def read_sector(self, sector): + """ + Reads data from the specified sector + Returns: a byte array containing the data + """ + self.set_alternate_safe(sector['alt']) + self.set_address_safe(sector['addr']) + + transfer_size = fractions.gcd(sector['len'], MAX_TRANSFER_SIZE) + #blocknum_offset = int((sector['addr'] - sector['baseaddr']) / transfer_size) + + + data = array.array(u'B') + for blocknum in range(int(sector['len'] / transfer_size)): + #print('read at {:08X}'.format(sector['addr'] + blocknum * TRANSFER_SIZE)) + deviceBlock = self.read(blocknum, transfer_size) + data.extend(deviceBlock) + self.abort() # take device into DFU_IDLE + return data + + def jump_to_application(self, address): + self.set_address_safe(address) + #self.set_address(address) + #status = self.wait_while_state(DfuState.DFU_DOWNLOAD_BUSY) + #if status[1] != DfuState.DFU_DOWNLOAD_IDLE: + # raise RuntimeError("An error occured. Device Status: {}".format(status[1])) + + self.leave() + status = self.wait_while_state(DfuState.DFU_MANIFEST_SYNC) + if status[1] != DfuState.DFU_MANIFEST: + raise RuntimeError("An error occured. Device Status: {}".format(status[1])) diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 050d4110..0a2d398f 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -363,3 +363,22 @@ class Logger(): def error(self, text): # TODO: write to stderr self.print_colored(self._prefix + text, Logger.COLOR_RED) + +def yes_no_prompt(question, default=None): + if default is None: + question += " [y/n] " + elif default == True: + question += " [Y/n] " + elif default == False: + question += " [y/N] " + + while True: + print(question, end='') + + choice = input().lower() + if choice in {'yes', 'y'}: + return True + elif choice in {'no', 'n'}: + return False + elif choice == '' and default is not None: + return default diff --git a/tools/odrive/version.py b/tools/odrive/version.py index 68bad7d0..b0b43035 100644 --- a/tools/odrive/version.py +++ b/tools/odrive/version.py @@ -4,6 +4,20 @@ import subprocess import os import sys +def version_str_to_tuple(version_string): + """ + Converts a version string to a tuple of the form + (major, minor, revision, prerelease) + + Example: "fw-v0.3.6-23" => (0, 3, 6, True) + """ + regex=r'.*v([0-9a-zA-Z]+).([0-9a-zA-Z]+).([0-9a-zA-Z]+)(.*)' + return (int(re.sub(regex, r"\1", version_string)), + int(re.sub(regex, r"\2", version_string)), + int(re.sub(regex, r"\3", version_string)), + (re.sub(regex, r"\4", version_string) != "")) + + def get_version_from_git(): script_dir = os.path.dirname(os.path.realpath(__file__)) try: @@ -12,19 +26,15 @@ def get_version_from_git(): cwd=script_dir) git_tag = git_tag.decode(sys.stdout.encoding).rstrip('\n') - regex=r'.*v([0-9a-zA-Z]).([0-9a-zA-Z]).([0-9a-zA-Z])(.*)' - package_version_major = int(re.sub(regex, r"\1", git_tag)) - package_version_minor = int(re.sub(regex, r"\2", git_tag)) - package_version_revision = int(re.sub(regex, r"\3", git_tag)) - package_version_unreleased = (re.sub(regex, r"\4", git_tag) != "") + (major, minor, revision, is_prerelease) = version_str_to_tuple(git_tag) - if package_version_unreleased: - package_version_revision += 1 + if is_prerelease: + revision += 1 + return git_tag, major, minor, revision, is_prerelease except Exception as ex: print(ex) return "[unknown version]", 0, 0, 0, 1 - return git_tag, package_version_major, package_version_minor, package_version_revision, package_version_unreleased def get_version_str(git_only=False): """ diff --git a/tools/odrivetool b/tools/odrivetool index 901ebde3..0e9374c2 100755 --- a/tools/odrivetool +++ b/tools/odrivetool @@ -33,8 +33,15 @@ shell_parser.add_argument("--no-ipython", action="store_true", "instead of the IPython shell, " "even if IPython is installed.") -dfu_parser = subparsers.add_parser('dfu', help="Upgrade the ODrive device firmware") -dfu_parser.add_argument('file', metavar='HEX', help='The .hex file to be flashed. Make sure your firmware board version matches the actual board version.') +dfu_parser = subparsers.add_parser('dfu', help="Upgrade the ODrive device firmware." + "If no serial number is specified, the first ODrive that is found is updated") +dfu_parser.add_argument('file', metavar='HEX', nargs='?', + help='The .hex file to be flashed. Make sure target board version ' + 'of the firmware file matches the actual board version. ' + 'You can download the latest release manually from ' + 'https://github.com/madcowswe/ODrive/releases. ' + 'If no file is provided, the script automatically downloads ' + 'the latest firmware.') subparsers.add_parser('liveplotter', help="Upgrade the ODrive's Firmware") subparsers.add_parser('drv-status', help="Show status of the on-board DRV8301 chips (for debugging only)") From 57702160107b0332037e1471d13ece9b96389e1a Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sat, 12 May 2018 15:26:51 -0700 Subject: [PATCH 178/215] save and restore configuration during DFU --- Firmware/MotorControl/main.cpp | 2 + tools/odrive/configuration.py | 82 +++++++++++++++++++++++++++++++ tools/odrive/dfu.py | 78 +++++++++++++++++------------ tools/odrive/discovery.py | 2 +- tools/odrive/shell.py | 2 +- tools/odrive/usbbulk_transport.py | 21 ++++---- tools/odrive/utils.py | 10 ++++ tools/odrivetool | 46 +++++++++++++++-- tools/run_tests.py | 6 +-- 9 files changed, 198 insertions(+), 51 deletions(-) create mode 100644 tools/odrive/configuration.py diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index ea3c155c..80013331 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -33,6 +33,8 @@ void save_configuration(void) { &motor_configs, &axis_configs)) { //printf("saving configuration failed\r\n"); osDelay(5); + } else { + user_config_loaded_ = true; } } diff --git a/tools/odrive/configuration.py b/tools/odrive/configuration.py new file mode 100644 index 00000000..8f297249 --- /dev/null +++ b/tools/odrive/configuration.py @@ -0,0 +1,82 @@ + +import json +import os +import tempfile +import odrive.remote_object +from odrive.utils import OperationAbortedException + +def get_dict(obj, is_config_object): + result = {} + for (k,v) in obj._remote_attributes.items(): + if isinstance(v, odrive.remote_object.RemoteProperty) and is_config_object: + result[k] = v.get_value() + elif isinstance(v, odrive.remote_object.RemoteObject): + sub_dict = get_dict(v, k == 'config') + if sub_dict != {}: + result[k] = sub_dict + return result + +def set_dict(obj, path, config_dict): + errors = [] + for (k,v) in config_dict.items(): + name = path + ("." if path != "" else "") + k + if not k in obj._remote_attributes: + errors.append("Could not restore {}: property not found on device".format(name)) + continue + remote_attribute = obj._remote_attributes[k] + if isinstance(remote_attribute, odrive.remote_object.RemoteObject): + errors += set_dict(remote_attribute, name, v) + else: + try: + remote_attribute.set_value(v) + except Exception as ex: + errors.append("Could not restore {}: {}".format(name, str(ex))) + return errors + +def get_temp_config_filename(device): + serial_number = odrive.utils.get_serial_number_str(device) + safe_serial_number = ''.join(filter(str.isalnum, serial_number)) + return os.path.join(tempfile.gettempdir(), 'odrive-config-{}.json'.format(safe_serial_number)) + +def backup_config(device, filename, logger): + """ + Exports the configuration of an ODrive to a JSON file. + If no file name is provided, the file is placed into a + temporary directory. + """ + + if filename is None: + filename = get_temp_config_filename(device) + + logger.info("Saving configuration to {}...".format(filename)) + + if os.path.exists(filename): + if not odrive.utils.yes_no_prompt("The file {} already exists. Do you want to override it?".format(filename), True): + raise OperationAbortedException() + + data = get_dict(device, False) + with open(filename, 'w') as file: + json.dump(data, file) + logger.info("Configuration saved.") + +def restore_config(device, filename, logger): + """ + Restores the configuration stored in a file + """ + + if filename is None: + filename = get_temp_config_filename(device) + + with open(filename) as file: + data = json.load(file) + + logger.info("Restoring configuration from {}...".format(filename)) + errors = odrive.configuration.set_dict(device, "", data) + + for error in errors: + logger.info(error) + if errors: + logger.warn("Some of the configuration could not be restored.") + + device.save_configuration() + logger.info("Configuration restored.") diff --git a/tools/odrive/dfu.py b/tools/odrive/dfu.py index 3e63887a..872a5d30 100755 --- a/tools/odrive/dfu.py +++ b/tools/odrive/dfu.py @@ -12,9 +12,10 @@ import struct import requests import re import io +import os import usb.core import odrive.discovery -from odrive.utils import Event +from odrive.utils import Event, OperationAbortedException from odrive.dfuse import * try: @@ -96,6 +97,8 @@ class Firmware(): @staticmethod def is_newer(a, b): + if (a[0], a[1], a[2]) == (0, 0, 0) or (b[0], b[1], b[2]) == (0, 0, 0): + return False # Cannot compare unknown versions return (a[0] > b[0] or (a[0] == b[0] and (a[1] > b[1] or @@ -154,6 +157,7 @@ class FirmwareFromGithub(Firmware): Returns the content of the firmware in as a binary array in Intel Hex format """ if self.hex is None: + print("Downloading firmware {}...".format(get_fw_version_string(self.fw_version))) response = requests.get('https://api.github.com/repos/madcowswe/ODrive/releases/assets/' + str(self.github_asset_id), headers={'Accept': 'application/octet-stream'}) if response.status_code != 200: @@ -249,7 +253,7 @@ def find_device_in_dfu_mode(serial_number, cancellation_token): time.sleep(1) return None -def update_device(device, firmware, verbose, cancellation_token): +def update_device(device, firmware, logger, cancellation_token): """ Updates the specified device with the specified firmware. The device passed to this function can either be in @@ -262,8 +266,8 @@ def update_device(device, firmware, verbose, cancellation_token): if isinstance(device, usb.core.Device): serial_number = device.serial_number dfudev = DfuDevice(device) - if (verbose): - print("OTP:") + if (logger._verbose): + logger.debug("OTP:") dump_otp(dfudev) # Read hardware version from one-time-programmable memory @@ -304,18 +308,18 @@ def update_device(device, firmware, verbose, cancellation_token): else: suggestion = 'Run "make write_otp" to program the board version.' raise Exception('Cannot check online for new firmware because the board version is unknown. ' + suggestion) - print("Checking online for newest firmware...") + print("Checking online for newest firmware...", end='') firmware = get_newest_firmware(hw_version) if firmware is None: raise Exception("could not find any firmware release for this board version") + print(" found {}".format(get_fw_version_string(firmware.fw_version))) - print("Updating to firmware {}".format(get_fw_version_string(firmware.fw_version))) if firmware < fw_version: print("Warning: you are about to flash firmware {} which is older than the firmware on the device ({}).".format( get_fw_version_string(firmware.fw_version), get_fw_version_string(fw_version))) if not odrive.utils.yes_no_prompt("Do you want to flash this firmware anyway?", True): - return + raise OperationAbortedException() # load hex file # TODO: Either use the elf format or pack a custom format with a manifest. @@ -323,10 +327,17 @@ def update_device(device, firmware, verbose, cancellation_token): # have to publish one file for every board (instead of elf AND hex files). hexfile = IntelHex(firmware.get_as_hex()) - if (verbose): - print("Contiguous segments in hex file:") - for start, end in hexfile.segments(): - print(" {:08X} to {:08X}".format(start, end - 1)) + logger.debug("Contiguous segments in hex file:") + for start, end in hexfile.segments(): + logger.debug(" {:08X} to {:08X}".format(start, end - 1)) + + # Back up configuration + if dfudev is None: + did_backup_config = device.user_config_loaded if hasattr(device, 'user_config_loaded') else False + if did_backup_config: + odrive.configuration.backup_config(device, None, logger) + elif not odrive.utils.yes_no_prompt("The configuration cannot be backed up because the device is already in DFU mode. The configuration may be lost after updating. Do you want to continue anyway?", True): + raise OperationAbortedException() # Put the device into DFU mode if it's not already in DFU mode if dfudev is None: @@ -334,21 +345,19 @@ def update_device(device, firmware, verbose, cancellation_token): stm_device = find_device_in_dfu_mode(serial_number, cancellation_token) dfudev = DfuDevice(stm_device) - if (verbose): - print("Sectors on device: ") - for sector in dfudev.sectors: - print(" {:08X} to {:08X} ({})".format( - sector['addr'], - sector['addr'] + sector['len'] - 1, - sector['name'])) + logger.debug("Sectors on device: ") + for sector in dfudev.sectors: + logger.debug(" {:08X} to {:08X} ({})".format( + sector['addr'], + sector['addr'] + sector['len'] - 1, + sector['name'])) # fill sectors with data touched_sectors = list(populate_sectors(dfudev.sectors, hexfile)) - if (verbose): - print("The following sectors will be flashed: ") - for sector,_ in touched_sectors: - print(" {:08X} to {:08X}".format(sector['addr'], sector['addr'] + sector['len'] - 1)) + logger.debug("The following sectors will be flashed: ") + for sector,_ in touched_sectors: + logger.debug(" {:08X} to {:08X}".format(sector['addr'], sector['addr'] + sector['len'] - 1)) # Erase try: @@ -394,16 +403,26 @@ def update_device(device, firmware, verbose, cancellation_token): # Jump to application dfudev.jump_to_application(0x08000000) -def launch_dfu(args, app_shutdown_token): + logger.info("Waiting for the device to reappear...") + device = odrive.discovery.find_any("usb", serial_number, + cancellation_token, cancellation_token, timeout=30) + + if did_backup_config: + odrive.configuration.restore_config(device, None, logger) + os.remove(odrive.configuration.get_temp_config_filename(device)) + + logger.success("Device firmware update successful.") + +def launch_dfu(args, logger, cancellation_token): """ Waits for a device that matches args.path and args.serial_number and then upgrades the device's firmware. """ serial_number = args.serial_number - find_odrive_cancellation_token = Event(app_shutdown_token) + find_odrive_cancellation_token = Event(cancellation_token) - print("Waiting for ODrive...") + logger.info("Waiting for ODrive...") devices = [None, None] @@ -416,18 +435,13 @@ def launch_dfu(args, app_shutdown_token): # Scan for ODrives not in DFU mode # We only scan on USB because DFU is only implemented over USB devices[1] = odrive.discovery.find_any("usb", serial_number, - find_odrive_cancellation_token, app_shutdown_token) + find_odrive_cancellation_token, cancellation_token) find_odrive_cancellation_token.set() device = devices[0] or devices[1] firmware = FirmwareFromFile(args.file) if args.file else None - try: - update_device(device, firmware, args.verbose, app_shutdown_token) - except Exception as ex: - if args.verbose: - raise - print(ex) + update_device(device, firmware, logger, cancellation_token) diff --git a/tools/odrive/discovery.py b/tools/odrive/discovery.py index a6536638..bc92ec68 100644 --- a/tools/odrive/discovery.py +++ b/tools/odrive/discovery.py @@ -62,7 +62,7 @@ def find_all(path, serial_number, return json_data = {"name": "odrive", "members": json_data} obj = odrive.remote_object.RemoteObject(json_data, None, channel, printer) - device_serial_number = format(obj.serial_number, 'x').upper() if hasattr(obj, 'serial_number') else "[unknown serial number]" + device_serial_number = odrive.utils.get_serial_number_str(obj) if serial_number != None and device_serial_number != serial_number: printer("Ignoring device with serial number {}".format(device_serial_number)) return diff --git a/tools/odrive/shell.py b/tools/odrive/shell.py index ef072010..7fc3e5ad 100644 --- a/tools/odrive/shell.py +++ b/tools/odrive/shell.py @@ -52,7 +52,7 @@ def did_discover_device(odrive, logger, app_shutdown_token): # Publish new ODrive to interactive console interactive_variables[interactive_name] = odrive globals()[interactive_name] = odrive # Add to globals so tab complete works - logger.info("{} to ODrive {:012X} as {}".format(verb, serial_number, interactive_name)) + logger.notify("{} to ODrive {:012X} as {}".format(verb, serial_number, interactive_name)) # Subscribe to disappearance of the device odrive.__channel__._channel_broken.subscribe(lambda: did_lose_device(interactive_name, logger, app_shutdown_token)) diff --git a/tools/odrive/usbbulk_transport.py b/tools/odrive/usbbulk_transport.py index eefc33e8..7461f021 100644 --- a/tools/odrive/usbbulk_transport.py +++ b/tools/odrive/usbbulk_transport.py @@ -154,15 +154,18 @@ def discover_channels(path, serial_number, callback, cancellation_token, channel known_devices = [] def device_matcher(device): #print(" test {:04X}:{:04X}".format(device.idVendor, device.idProduct)) - if (device.bus, device.address) in known_devices: - return False - if bus != None and device.bus != bus: - return False - if address != None and device.address != address: - return False - if serial_number != None and device.serial_number != serial_number: - return False - if (device.idVendor, device.idProduct) not in ODRIVE_VID_PID_PAIRS: + try: + if (device.bus, device.address) in known_devices: + return False + if bus != None and device.bus != bus: + return False + if address != None and device.address != address: + return False + if serial_number != None and device.serial_number != serial_number: + return False + if (device.idVendor, device.idProduct) not in ODRIVE_VID_PID_PAIRS: + return False + except: return False return True diff --git a/tools/odrive/utils.py b/tools/odrive/utils.py index 0a2d398f..d73cbba2 100755 --- a/tools/odrive/utils.py +++ b/tools/odrive/utils.py @@ -25,6 +25,9 @@ data_rate = 100 plot_rate = 10 num_samples = 1000 +class OperationAbortedException(Exception): + pass + def start_liveplotter(get_var_callback): """ Starts a liveplotter. @@ -157,6 +160,11 @@ def setup_udev_rules(logger): subprocess.run(["udevadm", "trigger"], check=True) logger.info('udev rules configured successfully') +def get_serial_number_str(device): + if hasattr(device, 'serial_number'): + return format(device.serial_number, 'x').upper() + else: + return "[unknown serial number]" ## Exceptions ## @@ -357,6 +365,8 @@ class Logger(): def success(self, text): self.print_colored(self._prefix + text, Logger.COLOR_GREEN) def info(self, text): + self.print_colored(self._prefix + text, Logger.COLOR_DEFAULT) + def notify(self, text): self.print_colored(self._prefix + text, Logger.COLOR_CYAN) def warn(self, text): self.print_colored(self._prefix + text, Logger.COLOR_YELLOW) diff --git a/tools/odrivetool b/tools/odrivetool index 0e9374c2..54433c10 100755 --- a/tools/odrivetool +++ b/tools/odrivetool @@ -7,7 +7,8 @@ from __future__ import print_function import sys import argparse import odrive.discovery -from odrive.utils import Logger, Event +from odrive.utils import Logger, Event, OperationAbortedException +from odrive.configuration import * # Flush stdout by default # Source: @@ -43,6 +44,17 @@ dfu_parser.add_argument('file', metavar='HEX', nargs='?', 'If no file is provided, the script automatically downloads ' 'the latest firmware.') + +dfu_parser = subparsers.add_parser('backup-config', help="Saves the configuration of the ODrive to a JSON file") +dfu_parser.add_argument('file', nargs='?', + help="Path to the file where to store the data. " + "If no path is provided, the configuration is stored in {}.".format(tempfile.gettempdir())) + +dfu_parser = subparsers.add_parser('restore-config', help="Restores the configuration of the ODrive from a JSON file") +dfu_parser.add_argument('file', nargs='?', + help="Path to the file that contains the configuration data. " + "If no path is provided, the configuration is loaded from {}.".format(tempfile.gettempdir())) + subparsers.add_parser('liveplotter', help="Upgrade the ODrive's Firmware") subparsers.add_parser('drv-status', help="Show status of the on-board DRV8301 chips (for debugging only)") subparsers.add_parser('rate-test', help="Estimate the average transmission bandwidth over USB") @@ -117,12 +129,14 @@ try: elif args.command == 'dfu': print_version() import odrive.dfu - odrive.dfu.launch_dfu(args, app_shutdown_token) + odrive.dfu.launch_dfu(args, logger, app_shutdown_token) elif args.command == 'liveplotter': from odrive.utils import start_liveplotter print("Waiting for ODrive...") - my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number) + my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number, + search_cancellation_token=app_shutdown_token, + channel_termination_token=app_shutdown_token) # If you want to plot different values, change them here. # You can plot any number of values concurrently. @@ -132,22 +146,44 @@ try: elif args.command == 'drv-status': from odrive.utils import print_drv_regs print("Waiting for ODrive...") - my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number) + my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number, + search_cancellation_token=app_shutdown_token, + channel_termination_token=app_shutdown_token) print_drv_regs("Motor 0", my_odrive.axis0.motor) print_drv_regs("Motor 1", my_odrive.axis1.motor) elif args.command == 'rate-test': from odrive.utils import rate_test print("Waiting for ODrive...") - my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number) + my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number, + search_cancellation_token=app_shutdown_token, + channel_termination_token=app_shutdown_token) rate_test(my_odrive) elif args.command == 'udev-setup': from odrive.utils import setup_udev_rules setup_udev_rules(logger) + elif args.command == 'backup-config': + from odrive.configuration import backup_config + print("Waiting for ODrive...") + my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number, + search_cancellation_token=app_shutdown_token, + channel_termination_token=app_shutdown_token) + backup_config(my_odrive, args.file, logger) + + elif args.command == 'restore-config': + from odrive.configuration import restore_config + print("Waiting for ODrive...") + my_odrive = odrive.discovery.find_any(path=args.path, serial_number=args.serial_number, + search_cancellation_token=app_shutdown_token, + channel_termination_token=app_shutdown_token) + restore_config(my_odrive, args.file, logger) + else: raise Exception("unknown command: " + args.command) +except OperationAbortedException: + logger.info("Operation aborted.") finally: app_shutdown_token.set() diff --git a/tools/run_tests.py b/tools/run_tests.py index 1cb1bcd8..447546e9 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -136,7 +136,7 @@ try: if isinstance(test, ODriveTest): def odrv_test_thread(odrv_name): odrv_ctx = odrives_by_name[odrv_name] - logger.info('* running {} on {}...'.format(type(test).__name__, odrv_name)) + logger.notify('* running {} on {}...'.format(type(test).__name__, odrv_name)) try: test.check_preconditions(odrv_ctx, logger.indent(' {}: '.format(odrv_name))) @@ -165,7 +165,7 @@ try: try: if not app_shutdown_token.is_set(): # Run test on this axis - logger.info('* running {} on {}...'.format(type(test).__name__, axis_name)) + logger.notify('* running {} on {}...'.format(type(test).__name__, axis_name)) try: test.check_preconditions(axis_ctx, logger.indent(' {}: '.format(axis_name))) @@ -197,7 +197,7 @@ try: try: if not app_shutdown_token.is_set(): # Run test on this axis - logger.info('* running {} on {}...'.format(type(test).__name__, coupling_name)) + logger.notify('* running {} on {}...'.format(type(test).__name__, coupling_name)) try: test.check_preconditions(coupled_axes[0], coupled_axes[1], logger.indent(' {}: '.format(coupling_name))) From 7288d93a00bb5104c08fe99da0a39a62cb27acde Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sat, 12 May 2018 17:35:08 -0700 Subject: [PATCH 179/215] update changelog --- Firmware/CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Firmware/CHANGELOG.md b/Firmware/CHANGELOG.md index 66a71605..0b43068e 100644 --- a/Firmware/CHANGELOG.md +++ b/Firmware/CHANGELOG.md @@ -11,7 +11,10 @@ Please add a note of your changes below this heading if you make a Pull Request. * System stats (e.g. stack usage) are exposed under `.system_stats` ### Changed -* The DFU script now verifies the flash after writing +* DFU script updates + * Verify the flash after writing + * Automatically download firmware from GitHub releases if no file is provided + * Retain configuration during firmware updates * Refactor python tools * The scripts `explore_odrive.py`, `liveplotter.py`, `drv_status.py` and `rate_test.py` have been merged into one single `odrivetool` script. Running this script without any arguments provides the shell that `explore_odrive.py` used to provide. * The command line options of `odrivetool` have changed compared to the original `explore_odrive.py`. See `odrivetool --help` for more details. @@ -19,6 +22,7 @@ Please add a note of your changes below this heading if you make a Pull Request. * No need to restart the `odrivetool` shell when devices get disconnected and reconnected * ODrive accesses from within python tools are now thread-safe. That means you can read from the same remote property from multiple threads concurrently. * The liveplotter (`odrivetool liveplotter`, formerly `liveplotter.py`) does no longer steal focus and closes as expected + * Add commands `odrivetool backup-config` and `odrivetool restore-config` * (experimental: start liveplotter from `odrivetool` shell by typing `start_liveplotter(lambda: odrv0.motor0.encoder.encoder_state)`) * `make write_otp` command to burn the board version onto the ODrive's one-time programmable memory. If you have an ODrive v3.4 or older, you can run this once for a better firmware update user experience in the future. Run the command without any options for more details. Once set, the board version is exposed through the `hw_version_[...]` properties. * bake Git-derived firmware version into firmware binary. The firmware version is exposed through the `fw_version_[...]` properties. From 705d18885e815929fc1dc3c9f64ccc46cf4729d1 Mon Sep 17 00:00:00 2001 From: samuelsadok Date: Sat, 12 May 2018 22:05:35 -0700 Subject: [PATCH 180/215] disable -fstack-usage --- Firmware/build.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Firmware/build.lua b/Firmware/build.lua index 8ef67627..ac86d7aa 100644 --- a/Firmware/build.lua +++ b/Firmware/build.lua @@ -49,7 +49,8 @@ end function GCCToolchain(prefix, builddir, compiler_flags, linker_flags) -- add some default compiler flags - compiler_flags += '-fstack-usage' + -- This gives a warning for some functions containing inline assembly (prvPortStartFirstTask in particular) + --compiler_flags += '-fstack-usage' gcc_generic_compiler = function(compiler, compiler_flags, gen_su_file, src, flags, includes, outputs) -- convert include list to flags From 1375dd1b3261d52782cc1c73d4935cc561f5d27b Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Sat, 12 May 2018 22:18:13 -0700 Subject: [PATCH 181/215] properly disable -fstack-usage --- Firmware/build.lua | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Firmware/build.lua b/Firmware/build.lua index ac86d7aa..ea792219 100644 --- a/Firmware/build.lua +++ b/Firmware/build.lua @@ -49,8 +49,12 @@ end function GCCToolchain(prefix, builddir, compiler_flags, linker_flags) -- add some default compiler flags - -- This gives a warning for some functions containing inline assembly (prvPortStartFirstTask in particular) - --compiler_flags += '-fstack-usage' + -- -fstack-usage gives a warning for some functions containing inline assembly (prvPortStartFirstTask in particular) + -- so for now we just disable it + calculate_stack_usage = false + if calculate_stack_usage then + compiler_flags += '-fstack-usage' + end gcc_generic_compiler = function(compiler, compiler_flags, gen_su_file, src, flags, includes, outputs) -- convert include list to flags @@ -80,8 +84,8 @@ function GCCToolchain(prefix, builddir, compiler_flags, linker_flags) } end return { - compile_c = function(src, flags, includes, outputs) gcc_generic_compiler(prefix..'gcc -std=c99', compiler_flags, true, src, flags, includes, outputs) end, - compile_cpp = function(src, flags, includes, outputs) gcc_generic_compiler(prefix..'g++ -std=c++14', compiler_flags, true, src, flags, includes, outputs) end, + compile_c = function(src, flags, includes, outputs) gcc_generic_compiler(prefix..'gcc -std=c99', compiler_flags, calculate_stack_usage, src, flags, includes, outputs) end, + compile_cpp = function(src, flags, includes, outputs) gcc_generic_compiler(prefix..'g++ -std=c++14', compiler_flags, calculate_stack_usage, src, flags, includes, outputs) end, compile_asm = function(src, flags, includes, outputs) gcc_generic_compiler(prefix..'gcc -x assembler-with-cpp', compiler_flags, false, src, flags, includes, outputs) end, link = function(objects, output_name) output_name = builddir..'/'..output_name From 75a7c0b012f5673fec8cd98c2fee7489370f6761 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 12 May 2018 22:14:52 -0700 Subject: [PATCH 182/215] add strict suggestion to tup config default --- Firmware/.vscode/c_cpp_properties.json | 2 +- Firmware/tup.config.default | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Firmware/.vscode/c_cpp_properties.json b/Firmware/.vscode/c_cpp_properties.json index eab99cd5..4b0ce3bf 100644 --- a/Firmware/.vscode/c_cpp_properties.json +++ b/Firmware/.vscode/c_cpp_properties.json @@ -129,5 +129,5 @@ } } ], - "version": 3 + "version": 4 } \ No newline at end of file diff --git a/Firmware/tup.config.default b/Firmware/tup.config.default index 8ebb4402..60d2500d 100644 --- a/Firmware/tup.config.default +++ b/Firmware/tup.config.default @@ -3,3 +3,6 @@ #CONFIG_BOARD_VERSION=v3.5-24V CONFIG_USB_PROTOCOL=native CONFIG_UART_PROTOCOL=ascii + +# Uncomment this to error on compilation warnings +#CONFIG_STRICT=true From 1ace6422db5b050e269c707f01ae45d40d113c0b Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 12 May 2018 22:20:41 -0700 Subject: [PATCH 183/215] explicit cast on num_steps --- Firmware/MotorControl/encoder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 6267e152..7bc76d0c 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -116,7 +116,7 @@ bool Encoder::run_offset_calibration() { static const float start_lock_duration = 1.0f; static const float scan_omega = 4.0f * M_PI; static const float scan_distance = 16.0f * M_PI; - static const int num_steps = scan_distance / scan_omega * current_meas_hz; + static const int num_steps = scan_distance / scan_omega * (float)current_meas_hz; // Temporarily disable index search so it doesn't mess // with the offset calibration From 94245a608b52412062d5822a62107b9a4772ad7d Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 12 May 2018 22:24:17 -0700 Subject: [PATCH 184/215] explicit cast on num_steps --- Firmware/MotorControl/encoder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 7bc76d0c..2c5de455 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -116,7 +116,7 @@ bool Encoder::run_offset_calibration() { static const float start_lock_duration = 1.0f; static const float scan_omega = 4.0f * M_PI; static const float scan_distance = 16.0f * M_PI; - static const int num_steps = scan_distance / scan_omega * (float)current_meas_hz; + static const int num_steps = (int)(scan_distance / scan_omega * (float)current_meas_hz); // Temporarily disable index search so it doesn't mess // with the offset calibration From 8b3b1017ba8b223a82dfb12b5df084c61d23f3bf Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 12 May 2018 22:36:44 -0700 Subject: [PATCH 185/215] Update .travis.yml --- .travis.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7260e431..b3639a02 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,17 +13,13 @@ addons: apt: packages: libc6-i386 + arm-none-eabi-gcc cache: directories: - "$HOME/dl" install: -- export GCC_DIR=$HOME/dl/gcc-arm-none-eabi-5_2-2015q4 -- export GCC_ARCHIVE=$HOME/dl/gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2 -- export GCC_URL=https://launchpad.net/gcc-arm-embedded/5.0/5-2015-q4-major/+download/gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2 -- if [ ! -e $GCC_DIR/bin/arm-none-eabi-gcc ]; then wget $GCC_URL -O $GCC_ARCHIVE; tar xfj $GCC_ARCHIVE -C $HOME/dl; fi -- export PATH=$PATH:$GCC_DIR/bin - export TUP_DIR=$HOME/dl/tup_0.7.5-0~16.04.york0_amd64 - export TUP_ARCHIVE=$HOME/dl/tup_0.7.5-0~16.04.york0_amd64.deb - export TUP_URL=http://ppa.launchpad.net/jonathonf/tup/ubuntu/pool/main/t/tup/tup_0.7.5-0~16.04.york0_amd64.deb From 9b44dc6f81efda816d636f2c4785308b55e43ae4 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 12 May 2018 22:38:46 -0700 Subject: [PATCH 186/215] Update .travis.yml --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index b3639a02..5ac3be5f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ addons: apt: packages: libc6-i386 - arm-none-eabi-gcc + gcc-arm-none-eabi cache: directories: From 37fbcff96d521e4708a7e79e98bfbe63786ce861 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 12 May 2018 22:39:52 -0700 Subject: [PATCH 187/215] Update .travis.yml --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5ac3be5f..f9146523 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,7 +12,6 @@ sudo: false addons: apt: packages: - libc6-i386 gcc-arm-none-eabi cache: From 3f720bb374ba13caa92a41ce24926a377a01e56b Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 12 May 2018 23:18:12 -0700 Subject: [PATCH 188/215] Revert "Update .travis.yml" --- .travis.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f9146523..7260e431 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,13 +12,18 @@ sudo: false addons: apt: packages: - gcc-arm-none-eabi + libc6-i386 cache: directories: - "$HOME/dl" install: +- export GCC_DIR=$HOME/dl/gcc-arm-none-eabi-5_2-2015q4 +- export GCC_ARCHIVE=$HOME/dl/gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2 +- export GCC_URL=https://launchpad.net/gcc-arm-embedded/5.0/5-2015-q4-major/+download/gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2 +- if [ ! -e $GCC_DIR/bin/arm-none-eabi-gcc ]; then wget $GCC_URL -O $GCC_ARCHIVE; tar xfj $GCC_ARCHIVE -C $HOME/dl; fi +- export PATH=$PATH:$GCC_DIR/bin - export TUP_DIR=$HOME/dl/tup_0.7.5-0~16.04.york0_amd64 - export TUP_ARCHIVE=$HOME/dl/tup_0.7.5-0~16.04.york0_amd64.deb - export TUP_URL=http://ppa.launchpad.net/jonathonf/tup/ubuntu/pool/main/t/tup/tup_0.7.5-0~16.04.york0_amd64.deb From dc7af0e9369faf909c12b23331940d6749865263 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sat, 12 May 2018 23:28:08 -0700 Subject: [PATCH 189/215] update arm gcc to newest install path --- .travis.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7260e431..8391da91 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,11 +19,18 @@ cache: - "$HOME/dl" install: -- export GCC_DIR=$HOME/dl/gcc-arm-none-eabi-5_2-2015q4 -- export GCC_ARCHIVE=$HOME/dl/gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2 -- export GCC_URL=https://launchpad.net/gcc-arm-embedded/5.0/5-2015-q4-major/+download/gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2 +# - export GCC_DIR=$HOME/dl/gcc-arm-none-eabi-5_2-2015q4 +# - export GCC_ARCHIVE=$HOME/dl/gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2 +# - export GCC_URL=https://launchpad.net/gcc-arm-embedded/5.0/5-2015-q4-major/+download/gcc-arm-none-eabi-5_2-2015q4-20151219-linux.tar.bz2 +# - if [ ! -e $GCC_DIR/bin/arm-none-eabi-gcc ]; then wget $GCC_URL -O $GCC_ARCHIVE; tar xfj $GCC_ARCHIVE -C $HOME/dl; fi +# - export PATH=$PATH:$GCC_DIR/bin + +- export GCC_DIR=$HOME/dl/gcc-arm-none-eabi-7-2017-q4-major +- export GCC_ARCHIVE=$HOME/dl/gcc-arm-none-eabi-7-2017-q4-major-linux.tar.bz2 +- export GCC_URL=https://developer.arm.com/-/media/Files/downloads/gnu-rm/7-2017q4/gcc-arm-none-eabi-7-2017-q4-major-linux.tar.bz2 - if [ ! -e $GCC_DIR/bin/arm-none-eabi-gcc ]; then wget $GCC_URL -O $GCC_ARCHIVE; tar xfj $GCC_ARCHIVE -C $HOME/dl; fi - export PATH=$PATH:$GCC_DIR/bin + - export TUP_DIR=$HOME/dl/tup_0.7.5-0~16.04.york0_amd64 - export TUP_ARCHIVE=$HOME/dl/tup_0.7.5-0~16.04.york0_amd64.deb - export TUP_URL=http://ppa.launchpad.net/jonathonf/tup/ubuntu/pool/main/t/tup/tup_0.7.5-0~16.04.york0_amd64.deb From ce1d6639e85bc83f8b2db59e4eaf057bcf1e7fe7 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Sun, 13 May 2018 20:03:40 -0700 Subject: [PATCH 190/215] use CAN not i2c by default --- Firmware/MotorControl/odrive_main.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 906545ad..bae964fb 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -58,7 +58,7 @@ extern SystemStats_t system_stats_; // @brief general user configurable board configuration struct BoardConfig_t { bool enable_uart = false; - bool enable_i2c_instead_of_can = true; + bool enable_i2c_instead_of_can = false; float brake_resistance = 0.47f; // [ohm] float dc_bus_undervoltage_trip_level = 8.0f; // Date: Mon, 14 May 2018 00:03:50 -0700 Subject: [PATCH 191/215] implement snap of requested current range --- Firmware/MotorControl/motor.cpp | 69 +++++++++++++++++++-------------- Firmware/MotorControl/motor.hpp | 8 ++-- 2 files changed, 44 insertions(+), 33 deletions(-) diff --git a/Firmware/MotorControl/motor.cpp b/Firmware/MotorControl/motor.cpp index 482bbf87..0c8707eb 100644 --- a/Firmware/MotorControl/motor.cpp +++ b/Firmware/MotorControl/motor.cpp @@ -65,42 +65,51 @@ void Motor::update_current_controller_gains() { // @brief Set up the gate drivers void Motor::DRV8301_setup() { - DRV_SPI_8301_Vars_t* local_regs = &gate_driver_regs_; - - DRV8301_enable(&gate_driver_); - DRV8301_setupSpi(&gate_driver_, local_regs); - - // TODO we can use reporting only if we actually wire up the nOCTW pin - local_regs->Ctrl_Reg_1.OC_MODE = DRV8301_OcMode_LatchShutDown; - // Overcurrent set to approximately 150A at 100degC. This may need tweaking. - local_regs->Ctrl_Reg_1.OC_ADJ_SET = DRV8301_VdsLevel_0p730_V; + // for reference: // 20V/V on 500uOhm gives a range of +/- 150A // 40V/V on 500uOhm gives a range of +/- 75A // 20V/V on 666uOhm gives a range of +/- 110A // 40V/V on 666uOhm gives a range of +/- 55A - local_regs->Ctrl_Reg_2.GAIN = DRV8301_ShuntAmpGain_80VpV; - // local_regs->Ctrl_Reg_2.GAIN = DRV8301_ShuntAmpGain_40VpV; - // local_regs->Ctrl_Reg_2.GAIN = DRV8301_ShuntAmpGain_20VpV; - switch (local_regs->Ctrl_Reg_2.GAIN) { - case DRV8301_ShuntAmpGain_10VpV: - phase_current_rev_gain_ = 1.0f / 10.0f; - break; - case DRV8301_ShuntAmpGain_20VpV: - phase_current_rev_gain_ = 1.0f / 20.0f; - break; - case DRV8301_ShuntAmpGain_40VpV: - phase_current_rev_gain_ = 1.0f / 40.0f; - break; - case DRV8301_ShuntAmpGain_80VpV: - phase_current_rev_gain_ = 1.0f / 80.0f; - break; - } + // Solve for exact gain, then snap down to have equal or larger range as requested + // or largest possible range otherwise + static const float kMargin = 0.90f; + static const float max_output_swing = 1.6f; // [V] out of amplifier + float max_unity_gain_current = kMargin * max_output_swing * hw_config_.shunt_conductance; // [A] + float requested_gain = max_unity_gain_current / config_.requested_current_range; // [V/V] - float margin = 0.90f; - float max_input = margin * 0.3f * hw_config_.shunt_conductance; - float max_swing = margin * 1.6f * hw_config_.shunt_conductance * phase_current_rev_gain_; - current_control_.max_allowed_current = std::min(max_input, max_swing); + // Decoding array for snapping gain + std::array, 4> gain_choices = { + std::make_pair(10.0f, DRV8301_ShuntAmpGain_10VpV), + std::make_pair(20.0f, DRV8301_ShuntAmpGain_20VpV), + std::make_pair(40.0f, DRV8301_ShuntAmpGain_40VpV), + std::make_pair(80.0f, DRV8301_ShuntAmpGain_80VpV) + }; + + // We use lower_bound in reverse because it snaps up by default, we want to snap down. + auto gain_snap_down = std::lower_bound(gain_choices.crbegin(), gain_choices.crend(), requested_gain, + [](std::pair pair, float val){ + return pair.first > val; + }); + + // If we snap to outside the array, clip to smallest val + if(gain_snap_down == gain_choices.crend()) + --gain_snap_down; + + // Values for current controller + phase_current_rev_gain_ = 1.0f / gain_snap_down->first; + // Clip all current control to actual usable range + current_control_.max_allowed_current = max_unity_gain_current * phase_current_rev_gain_; + + // We now have the gain settings we want to use, lets set up DRV chip + DRV_SPI_8301_Vars_t* local_regs = &gate_driver_regs_; + DRV8301_enable(&gate_driver_); + DRV8301_setupSpi(&gate_driver_, local_regs); + + local_regs->Ctrl_Reg_1.OC_MODE = DRV8301_OcMode_LatchShutDown; + // Overcurrent set to approximately 150A at 100degC. This may need tweaking. + local_regs->Ctrl_Reg_1.OC_ADJ_SET = DRV8301_VdsLevel_0p730_V; + local_regs->Ctrl_Reg_2.GAIN = gain_snap_down->second; local_regs->SndCmd = true; DRV8301_writeData(&gate_driver_, local_regs); diff --git a/Firmware/MotorControl/motor.hpp b/Firmware/MotorControl/motor.hpp index 961c05ce..852a64f4 100644 --- a/Firmware/MotorControl/motor.hpp +++ b/Firmware/MotorControl/motor.hpp @@ -46,9 +46,10 @@ typedef struct { Motor_type_t motor_type = MOTOR_TYPE_HIGH_CURRENT; // Read out max_allowed_current to see max supported value for current_lim. - // You can change DRV8301_ShuntAmpGain to get a different range. - // float current_lim = 75.0f; //[A] + // float current_lim = 70.0f; //[A] float current_lim = 10.0f; //[A] + // Value used to compute shunt amplifier gains + float requested_current_range = 70.0f; // [A] } MotorConfig_t; class Motor { @@ -207,7 +208,8 @@ public: make_protocol_property("phase_resistance", &config_.phase_resistance), make_protocol_property("direction", &config_.direction), make_protocol_property("motor_type", &config_.motor_type), - make_protocol_property("current_lim", &config_.current_lim) + make_protocol_property("current_lim", &config_.current_lim), + make_protocol_property("requested_current_range", &config_.requested_current_range) ) ); } From 439a32f5302f9cc72de0b6ebe7f86c8b2b2b9002 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 14 May 2018 00:18:27 -0700 Subject: [PATCH 192/215] reset encoder bandwidth back up to 1000 per s --- Firmware/MotorControl/encoder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index b8428c57..82066fd0 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -9,7 +9,7 @@ Encoder::Encoder(const EncoderHardwareConfig_t& hw_config, { // Calculate encoder pll gains // This calculation is currently identical to the PLL in SensorlessEstimator - float pll_bandwidth = 100.0f; // [rad/s] + float pll_bandwidth = 1000.0f; // [rad/s] pll_kp_ = 2.0f * pll_bandwidth; // Critically damped From cafcdb4e29a15691f46413b8ff23d7e933de7765 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Mon, 14 May 2018 01:53:01 -0700 Subject: [PATCH 193/215] center-align hall interpolation float offset --- Firmware/MotorControl/encoder.cpp | 41 +++++++++++++++++-------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/Firmware/MotorControl/encoder.cpp b/Firmware/MotorControl/encoder.cpp index 82066fd0..289f97f1 100644 --- a/Firmware/MotorControl/encoder.cpp +++ b/Firmware/MotorControl/encoder.cpp @@ -218,7 +218,7 @@ bool Encoder::run_offset_calibration() { offset_ = encvaluesum / (num_steps * 2); config_.offset = offset_; int32_t residual = encvaluesum - ((int64_t)offset_ * (int64_t)(num_steps * 2)); - config_.offset_float = (float)residual / (float)(num_steps * 2); + config_.offset_float = (float)residual / (float)(num_steps * 2) + 0.5f; // add 0.5 to center-align state to phase is_ready_ = true; config_.use_index = old_use_index; return true; @@ -276,11 +276,32 @@ bool Encoder::update() { count_in_cpr_ += delta_enc; count_in_cpr_ = mod(count_in_cpr_, config_.cpr); + //// run pll (for now pll is in units of encoder counts) + // Predict current pos + pos_estimate_ += current_meas_period * pll_vel_; + pos_cpr_ += current_meas_period * pll_vel_; + // discrete phase detector + float delta_pos = (float)(shadow_count_ - (int32_t)floorf(pos_estimate_)); + float delta_pos_cpr = (float)(count_in_cpr_ - (int32_t)floorf(pos_cpr_)); + delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * (float)(config_.cpr)); + // pll feedback + pos_estimate_ += current_meas_period * pll_kp_ * delta_pos; + pos_cpr_ += current_meas_period * pll_kp_ * delta_pos_cpr; + pos_cpr_ = fmodf_pos(pos_cpr_, (float)(config_.cpr)); + pll_vel_ += current_meas_period * pll_ki_ * delta_pos_cpr; + bool snap_to_zero_vel = false; + if (fabsf(pll_vel_) < 0.5f * current_meas_period * pll_ki_) { + pll_vel_ = 0.0f; //align delta-sigma on zero to prevent jitter + snap_to_zero_vel = true; + } //// run encoder count interpolation int32_t corrected_enc = count_in_cpr_ - offset_; + // if we are stopped, make sure we don't randomly drift + if (snap_to_zero_vel) { + interpolation_ = 0.5f; // reset interpolation if encoder edge comes - if (delta_enc > 0) { + } else if (delta_enc > 0) { interpolation_ = 0.0f; } else if (delta_enc < 0) { interpolation_ = 1.0f; @@ -300,21 +321,5 @@ bool Encoder::update() { // ph = fmodf(ph, 2*M_PI); phase_ = wrap_pm_pi(ph); - //// run pll (for now pll is in units of encoder counts) - // Predict current pos - pos_estimate_ += current_meas_period * pll_vel_; - pos_cpr_ += current_meas_period * pll_vel_; - // discrete phase detector - float delta_pos = (float)(shadow_count_ - (int32_t)floorf(pos_estimate_)); - float delta_pos_cpr = (float)(count_in_cpr_ - (int32_t)floorf(pos_cpr_)); - delta_pos_cpr = wrap_pm(delta_pos_cpr, 0.5f * (float)(config_.cpr)); - // pll feedback - pos_estimate_ += current_meas_period * pll_kp_ * delta_pos; - pos_cpr_ += current_meas_period * pll_kp_ * delta_pos_cpr; - pos_cpr_ = fmodf_pos(pos_cpr_, (float)(config_.cpr)); - pll_vel_ += current_meas_period * pll_ki_ * delta_pos_cpr; - if (fabsf(pll_vel_) < 0.5f * current_meas_period * pll_ki_) - pll_vel_ = 0.0f; //align delta-sigma on zero to prevent jitter - return true; } From 36b13a3d27d70105eab9897fc6b9b025500a1da7 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 14 May 2018 13:55:33 -0700 Subject: [PATCH 194/215] fix protocol functions with arguments It invoking functions that take arguments via the protocol was temporarily broken. Such functions were invoked with undefined arguments (usually 0). --- Firmware/communication/protocol.hpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Firmware/communication/protocol.hpp b/Firmware/communication/protocol.hpp index 87ef82cc..f51fc2de 100644 --- a/Firmware/communication/protocol.hpp +++ b/Firmware/communication/protocol.hpp @@ -812,6 +812,18 @@ public: LOG_PROTO("my tuple is at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); } + // The custom copy constructor is needed because otherwise the + // input_properties_ and output_properties_ would point to memory + // locations of the old object. + ProtocolFunction(const ProtocolFunction& other) : + name_(other.name_), obj_(other.obj_), func_ptr_(other.func_ptr_), + input_names_{other.input_names_}, output_names_{other.output_names_}, + input_properties_(PropertyListFactory::template make_property_list<0>(input_names_, in_args_)), + output_properties_(PropertyListFactory::template make_property_list<0>(output_names_, out_args_)) + { + LOG_PROTO("COPIED! my tuple is at %x and of size %u\r\n", (uintptr_t)&in_args_, sizeof(in_args_)); + } + void write_json(size_t id, StreamSink* output) { // write name write_string("{\"name\":\"", output); From 8bb52e353b4f556e0628a64e8263faf4121d7236 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 14 May 2018 14:40:09 -0700 Subject: [PATCH 195/215] fix docstring, simplify version comparision --- tools/odrive/dfu.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tools/odrive/dfu.py b/tools/odrive/dfu.py index 872a5d30..0f7b458d 100755 --- a/tools/odrive/dfu.py +++ b/tools/odrive/dfu.py @@ -77,8 +77,8 @@ def get_first_mismatch_index(array1, array2): def dump_otp(dfudev): """ Dumps the contents of the one-time-programmable - memory. The OTP will be used in future versions of - this script to determine the board version. + memory for debugging purposes. + The OTP is used to determine the board version. """ # 512 Byte OTP otp_sector = [s for s in dfudev.sectors if s['name'] == 'OTP Memory' and s['addr'] == 0x1fff7800][0] @@ -97,15 +97,11 @@ class Firmware(): @staticmethod def is_newer(a, b): - if (a[0], a[1], a[2]) == (0, 0, 0) or (b[0], b[1], b[2]) == (0, 0, 0): + a_num = (a[0], a[1], a[2]) + b_num = (b[0], b[1], b[2]) + if a_num == (0, 0, 0) or b_num == (0, 0, 0): return False # Cannot compare unknown versions - return (a[0] > b[0] or - (a[0] == b[0] and - (a[1] > b[1] or - (a[1] == b[1] and - (a[2] > b[2] or - (a[2] == b[2] and - (not a[3] and a[3]))))))) + return a_num > b_num or (a_num == b_num and not a[3] and b[3]) def __gt__(self, other): """ From 112f4a186496648992583bed0e70e880c5872ec4 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 14 May 2018 15:19:10 -0700 Subject: [PATCH 196/215] fix undefined variable when enabling ascii protocol --- Firmware/communication/interface_usb.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/Firmware/communication/interface_usb.cpp b/Firmware/communication/interface_usb.cpp index cee94a99..28891a82 100644 --- a/Firmware/communication/interface_usb.cpp +++ b/Firmware/communication/interface_usb.cpp @@ -47,7 +47,6 @@ public: } } usb_packet_output; -#if !defined(USB_PROTOCOL_NATIVE) class TreatPacketSinkAsStreamSink : public StreamSink { public: TreatPacketSinkAsStreamSink(PacketSink& output) : output_(output) {} @@ -66,7 +65,6 @@ public: private: PacketSink& output_; } usb_stream_output(usb_packet_output); -#endif #if defined(USB_PROTOCOL_NATIVE) BidirectionalPacketBasedChannel usb_channel(usb_packet_output); From 264ccff9d609817ba0ada3a1f4ac2dbeab2b1e1c Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 13 Mar 2018 19:35:59 -0700 Subject: [PATCH 197/215] Prevent the device from being identified as a CDC device Sets bDeviceClass, bDeviceSubClass, bDeviceProtocol, bInterfaceClass, bInterfaceSubClass and bInterfaceProtocol to 0 and remove all functional descriptors. This means the ODrive is no longer recognized as communications device class (CDC) device, thus avoiding problems where the OS or other applications try to communicate with or hog the device. Related resources: http://www.microchip.com/forums/m789836.aspx https://www-user.tu-chemnitz.de/~heha/viewchm.php/hs/usb.chm/usb5.htm Tested on Linux: the device no longer shows up as tty. To be tested on macOS and Windows. --- .../Class/CDC/Inc/usbd_cdc.h | 2 +- .../Class/CDC/Src/usbd_cdc.c | 30 +++++++++---------- Firmware/Board/v3/Src/usbd_desc.c | 4 +-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc/usbd_cdc.h b/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc/usbd_cdc.h index d937b2e8..d88fc069 100644 --- a/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc/usbd_cdc.h +++ b/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc/usbd_cdc.h @@ -58,7 +58,7 @@ #define CDC_DATA_FS_MAX_PACKET_SIZE 64 /* Endpoint IN & OUT Packet size */ #define CDC_CMD_PACKET_SIZE 8 /* Control Endpoint Packet size */ -#define USB_CDC_CONFIG_DESC_SIZ 67 +#define USB_CDC_CONFIG_DESC_SIZ (67 - 19) #define CDC_DATA_HS_IN_PACKET_SIZE CDC_DATA_HS_MAX_PACKET_SIZE #define CDC_DATA_HS_OUT_PACKET_SIZE CDC_DATA_HS_MAX_PACKET_SIZE diff --git a/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c b/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c index 24465641..56691dd9 100644 --- a/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c +++ b/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c @@ -198,11 +198,11 @@ __ALIGN_BEGIN uint8_t USBD_CDC_CfgHSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END = 0x00, /* bInterfaceNumber: Number of Interface */ 0x00, /* bAlternateSetting: Alternate setting */ 0x01, /* bNumEndpoints: One endpoints used */ - 0x02, /* bInterfaceClass: Communication Interface Class */ - 0x02, /* bInterfaceSubClass: Abstract Control Model */ - 0x01, /* bInterfaceProtocol: Common AT commands */ + 0x00, /* bInterfaceClass: Communication Interface Class */ + 0x00, /* bInterfaceSubClass: Abstract Control Model */ + 0x00, /* bInterfaceProtocol: Common AT commands */ 0x00, /* iInterface: */ - +#if 0 /*Header Functional Descriptor*/ 0x05, /* bLength: Endpoint Descriptor size */ 0x24, /* bDescriptorType: CS_INTERFACE */ @@ -229,7 +229,7 @@ __ALIGN_BEGIN uint8_t USBD_CDC_CfgHSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END = 0x06, /* bDescriptorSubtype: Union func desc */ 0x00, /* bMasterInterface: Communication class interface */ 0x01, /* bSlaveInterface0: Data Class Interface */ - +#endif /*Endpoint 2 Descriptor*/ 0x07, /* bLength: Endpoint Descriptor size */ USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */ @@ -294,11 +294,11 @@ __ALIGN_BEGIN uint8_t USBD_CDC_CfgFSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END = 0x00, /* bInterfaceNumber: Number of Interface */ 0x00, /* bAlternateSetting: Alternate setting */ 0x01, /* bNumEndpoints: One endpoints used */ - 0x02, /* bInterfaceClass: Communication Interface Class */ - 0x02, /* bInterfaceSubClass: Abstract Control Model */ - 0x01, /* bInterfaceProtocol: Common AT commands */ + 0x00, /* bInterfaceClass: Communication Interface Class */ + 0x00, /* bInterfaceSubClass: Abstract Control Model */ + 0x00, /* bInterfaceProtocol: Common AT commands */ 0x00, /* iInterface: */ - +#if 0 /*Header Functional Descriptor*/ 0x05, /* bLength: Endpoint Descriptor size */ 0x24, /* bDescriptorType: CS_INTERFACE */ @@ -325,7 +325,7 @@ __ALIGN_BEGIN uint8_t USBD_CDC_CfgFSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END = 0x06, /* bDescriptorSubtype: Union func desc */ 0x00, /* bMasterInterface: Communication class interface */ 0x01, /* bSlaveInterface0: Data Class Interface */ - +#endif /*Endpoint 2 Descriptor*/ 0x07, /* bLength: Endpoint Descriptor size */ USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */ @@ -385,11 +385,11 @@ __ALIGN_BEGIN uint8_t USBD_CDC_OtherSpeedCfgDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIG 0x00, /* bInterfaceNumber: Number of Interface */ 0x00, /* bAlternateSetting: Alternate setting */ 0x01, /* bNumEndpoints: One endpoints used */ - 0x02, /* bInterfaceClass: Communication Interface Class */ - 0x02, /* bInterfaceSubClass: Abstract Control Model */ - 0x01, /* bInterfaceProtocol: Common AT commands */ + 0x00, /* bInterfaceClass: Communication Interface Class */ + 0x00, /* bInterfaceSubClass: Abstract Control Model */ + 0x00, /* bInterfaceProtocol: Common AT commands */ 0x00, /* iInterface: */ - +#if 0 /*Header Functional Descriptor*/ 0x05, /* bLength: Endpoint Descriptor size */ 0x24, /* bDescriptorType: CS_INTERFACE */ @@ -416,7 +416,7 @@ __ALIGN_BEGIN uint8_t USBD_CDC_OtherSpeedCfgDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIG 0x06, /* bDescriptorSubtype: Union func desc */ 0x00, /* bMasterInterface: Communication class interface */ 0x01, /* bSlaveInterface0: Data Class Interface */ - +#endif /*Endpoint 2 Descriptor*/ 0x07, /* bLength: Endpoint Descriptor size */ USB_DESC_TYPE_ENDPOINT , /* bDescriptorType: Endpoint */ diff --git a/Firmware/Board/v3/Src/usbd_desc.c b/Firmware/Board/v3/Src/usbd_desc.c index 856d05e2..b8d6efac 100644 --- a/Firmware/Board/v3/Src/usbd_desc.c +++ b/Firmware/Board/v3/Src/usbd_desc.c @@ -189,8 +189,8 @@ __ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = 0x00, /*bcdUSB */ #endif /* (USBD_LPM_ENABLED == 1) */ 0x02, - 0x02, /*bDeviceClass*/ - 0x02, /*bDeviceSubClass*/ + 0x00, /*bDeviceClass*/ + 0x00, /*bDeviceSubClass*/ 0x00, /*bDeviceProtocol*/ USB_MAX_EP0_SIZE, /*bMaxPacketSize*/ LOBYTE(USBD_VID), /*idVendor*/ From 3fea671aaccafbb48ae7c55d19209f46baa07044 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 17 Apr 2018 16:32:42 -0700 Subject: [PATCH 198/215] add WinUSB compatibility descriptors sources: https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/automatic-installation-of-winusb http://searchingforbit.blogspot.com/2014/05/winusb-communication-with-stm32-round-2.html https://github.com/MeDzIeDaRbI/found-bits/blob/db3d9b51692bc71df3b06e34b4084397b07bbd75/Sources/Projects/Libs/STM32/STM32F4xxCommWinUSB/usbd_WinUSBComm.c --- Firmware/Board/v3/Inc/usbd_conf.h | 3 +- .../Class/CDC/Src/usbd_cdc.c | 265 ++++++++++++++++++ Firmware/Board/v3/Src/usbd_conf.c | 17 ++ 3 files changed, 284 insertions(+), 1 deletion(-) diff --git a/Firmware/Board/v3/Inc/usbd_conf.h b/Firmware/Board/v3/Inc/usbd_conf.h index bf276186..fe7867dd 100644 --- a/Firmware/Board/v3/Inc/usbd_conf.h +++ b/Firmware/Board/v3/Inc/usbd_conf.h @@ -89,6 +89,7 @@ * @brief Defines for configuration of the Usb device. * @{ */ +#define MS_VendorCode 'P' /*---------- -----------*/ #define USBD_MAX_NUM_INTERFACES 1 @@ -97,7 +98,7 @@ /*---------- -----------*/ #define USBD_MAX_STR_DESC_SIZ 512 /*---------- -----------*/ -#define USBD_SUPPORT_USER_STRING 0 +#define USBD_SUPPORT_USER_STRING 1 /*---------- -----------*/ #define USBD_DEBUG_LEVEL 0 /*---------- -----------*/ diff --git a/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c b/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c index 56691dd9..d1465831 100644 --- a/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c +++ b/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c @@ -132,6 +132,9 @@ static uint8_t *USBD_CDC_GetOtherSpeedCfgDesc (uint16_t *length); uint8_t *USBD_CDC_GetDeviceQualifierDescriptor (uint16_t *length); +static uint8_t USBD_WinUSBComm_SetupVendor(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req); +static uint8_t * USBD_WinUSBComm_GetUsrStrDescriptor(struct _USBD_HandleTypeDef *pdev, uint8_t index, uint16_t *length); + /* USB Standard Device Descriptor */ __ALIGN_BEGIN static uint8_t USBD_CDC_DeviceQualifierDesc[USB_LEN_DEV_QUALIFIER_DESC] __ALIGN_END = { @@ -173,6 +176,7 @@ USBD_ClassTypeDef USBD_CDC = USBD_CDC_GetFSCfgDesc, USBD_CDC_GetOtherSpeedCfgDesc, USBD_CDC_GetDeviceQualifierDescriptor, + USBD_WinUSBComm_GetUsrStrDescriptor }; /* USB CDC device Configuration Descriptor */ @@ -648,6 +652,9 @@ static uint8_t USBD_CDC_Setup (USBD_HandleTypeDef *pdev, case USB_REQ_SET_INTERFACE : break; } + + case USB_REQ_TYPE_VENDOR: + return USBD_WinUSBComm_SetupVendor(pdev, req); default: break; @@ -911,6 +918,264 @@ uint8_t USBD_CDC_ReceivePacket(USBD_HandleTypeDef *pdev) return USBD_FAIL; } } + +#if 0 +// Microsoft OS 2.0 Descriptor Set + +uint8_t ms_os_20_descriptor_set[0x9E] = { + +0x0A, 0x00, // Descriptor size (10 bytes) +0x00, 0x00, // MS OS 2.0 descriptor set header +0x00, 0x00, 0x03, 0x06, // Windows version (8.1) (0x06030000) +0x9E, 0x00, // Size, MS OS 2.0 descriptor set (158 bytes) + +// Microsoft OS 2.0 compatible ID descriptor + +0x14, 0x00, // Descriptor size (20 bytes) +0x03, 0x00, // MS OS 2.0 compatible ID descriptor +0x57, 0x49, 0x4E, 0x55, 0x53, 0x42, 0x00, 0x00, // WINUSB string +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Sub-compatible ID + +// Registry property descriptor + +0x80, 0x00, // Descriptor size (130 bytes) +0x04, 0x00, // Registry Property descriptor +0x01, 0x00, // Strings are null-terminated Unicode +0x28, 0x00, // Size of Property Name (40 bytes) + +//Property Name ("DeviceInterfaceGUID") + +0x44, 0x00, 0x65, 0x00, 0x76, 0x00, 0x69, 0x00, 0x63, 0x00, 0x65, 0x00, +0x49, 0x00, 0x6E, 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0x66, 0x00, +0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x47, 0x00, 0x55, 0x00, 0x49, 0x00, +0x44, 0x00, 0x00, 0x00, + +0x4E, 0x00, // Size of Property Data (78 bytes) + +// Vendor-defined Property Data: {ecceff35-146c-4ff3-acd9-8f992d09acdd} + +0x7B, 0x00, 0x65, 0x00, 0x63, 0x00, 0x63, 0x00, 0x65, 0x00, 0x66, 0x00, +0x66, 0x00, 0x33, 0x00, 0x35, 0x00, 0x2D, 0x00, 0x31, 0x00, 0x34, 0x00, +0x36, 0x00, 0x33, 0x00, 0x2D, 0x00, 0x34, 0x00, 0x66, 0x00, 0x66, 0x00, +0x33, 0x00, 0x2D, 0x00, 0x61, 0x00, 0x63, 0x00, 0x64, 0x00, 0x39, 0x00, +0x2D, 0x00, 0x38, 0x00, 0x66, 0x00, 0x39, 0x00, 0x39, 0x00, 0x32, 0x00, +0x64, 0x00, 0x30, 0x00, 0x39, 0x00, 0x61, 0x00, 0x63, 0x00, 0x64, 0x00, +0x64, 0x00, 0x7D, 0x00, 0x00, 0x00 +}; +#endif + + +// MS OS String descriptor to tell Windows that it may query for other descriptors +// It's a standard string descriptor. +// Windows will only query for OS descriptors once! +// Delete the information about already queried devices in registry by deleting: +// HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\usbflags\VVVVPPPPRRRR +__ALIGN_BEGIN uint8_t USBD_WinUSBComm_MS_OS_StringDescriptor[] __ALIGN_END = +{ + 0x12, // bLength 1 0x12 Length of the descriptor + 0x03, // bDescriptorType 1 0x03 Descriptor type + // qwSignature 14 ‘MSFT100’ Signature field + 0x4D, 0x00, // 'M' + 0x53, 0x00, // 'S' + 0x46, 0x00, // 'F' + 0x54, 0x00, // 'T' + 0x31, 0x00, // '1' + 0x30, 0x00, // '0' + 0x30, 0x00, // '0' + MS_VendorCode, // bMS_VendorCode 1 Vendor-specific Vendor code + 0x00 // bPad 1 0x00 Pad field +}; + + +/** +* @brief GetUsrStrDescriptor +* return non standard string descriptor (OS String Descriptor) +* @param pdev: device instance +* @param index : descriptor index (0xEE for MS OS String Descriptor) +* @param length : pointer data length +* @retval pointer to descriptor buffer +*/ +static uint8_t * USBD_WinUSBComm_GetUsrStrDescriptor(struct _USBD_HandleTypeDef *pdev, uint8_t index, uint16_t *length) +{ + *length = 0; + if ( 0xEE == index ) + { + *length = sizeof (USBD_WinUSBComm_MS_OS_StringDescriptor); + return USBD_WinUSBComm_MS_OS_StringDescriptor; + } + return NULL; +} + + +#define NUM_INTERFACES 2 + +#if NUM_INTERFACES == 2 +#define USB_WINUSBCOMM_COMPAT_ID_OS_DESC_SIZ (16 + 24 + 24) +#else +#define USB_WINUSBCOMM_COMPAT_ID_OS_DESC_SIZ (16 + 24) +#endif + + +// This associates winusb driver with the device +__ALIGN_BEGIN uint8_t USBD_WinUSBComm_Extended_Compat_ID_OS_Desc[USB_WINUSBCOMM_COMPAT_ID_OS_DESC_SIZ] __ALIGN_END = +{ + // +-- Offset in descriptor + // | +-- Size + // v v + USB_WINUSBCOMM_COMPAT_ID_OS_DESC_SIZ, 0, 0, 0, // 0 dwLength 4 DWORD The length, in bytes, of the complete extended compat ID descriptor + 0x00, 0x01, // 4 bcdVersion 2 BCD The descriptor’s version number, in binary coded decimal (BCD) format + 0x04, 0x00, // 6 wIndex 2 WORD An index that identifies the particular OS feature descriptor + 2, // 8 bCount 1 BYTE The number of custom property sections + 0, 0, 0, 0, 0, 0, 0, // 9 RESERVED 7 BYTEs Reserved + // ===================== + // 16 + + // +-- Offset from function section start + // | +-- Size + // v v + 0, // 0 bFirstInterfaceNumber 1 BYTE The interface or function number + 0, // 1 RESERVED 1 BYTE Reserved + 0x57, 0x49, 0x4E, 0x55, 0x53, 0x42, 0x00, 0x00, // 2 compatibleID 8 BYTEs The function’s compatible ID ("WINUSB") + 0, 0, 0, 0, 0, 0, 0, 0, // 10 subCompatibleID 8 BYTEs The function’s subcompatible ID + 0, 0, 0, 0, 0, 0, // 18 RESERVED 6 BYTEs Reserved + // ================================= + // 24 +#if NUM_INTERFACES == 2 + // +-- Offset from function section start + // | +-- Size + // v v + 1, // 0 bFirstInterfaceNumber 1 BYTE The interface or function number + 0, // 1 RESERVED 1 BYTE Reserved + 0x57, 0x49, 0x4E, 0x55, 0x53, 0x42, 0x00, 0x00, // 2 compatibleID 8 BYTEs The function’s compatible ID ("WINUSB") + 0, 0, 0, 0, 0, 0, 0, 0, // 10 subCompatibleID 8 BYTEs The function’s subcompatible ID + 0, 0, 0, 0, 0, 0, // 18 RESERVED 6 BYTEs Reserved + // ================================= + // 24 +#endif +}; + + +// Properties are added to: +// HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\VID_xxxx&PID_xxxx\sssssssss\Device Parameters +// Use USBDeview or similar to uninstall + +__ALIGN_BEGIN uint8_t USBD_WinUSBComm_Extended_Properties_OS_Desc[0xCC] __ALIGN_END = +{ + 0xCC, 0x00, 0x00, 0x00, // 0 dwLength 4 DWORD The length, in bytes, of the complete extended properties descriptor + 0x00, 0x01, // 4 bcdVersion 2 BCD The descriptor’s version number, in binary coded decimal (BCD) format + 0x05, 0x00, // 6 wIndex 2 WORD The index for extended properties OS descriptors + 0x02, 0x00, // 8 wCount 2 WORD The number of custom property sections that follow the header section + // ==================== + // 10 +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + 0x84, 0x00, 0x00, 0x00, // 0 dwSize 4 DWORD The size of this custom properties section + 0x01, 0x00, 0x00, 0x00, // 4 dwPropertyDataType 4 DWORD Property data format + 0x28, 0x00, // 8 wPropertyNameLength 2 DWORD Property name length + // ======================================== + // 10 + // 10 bPropertyName PNL WCHAR[] The property name + 'D',0, 'e',0, 'v',0, 'i',0, 'c',0, 'e',0, 'I',0, 'n',0, + 't',0, 'e',0, 'r',0, 'f',0, 'a',0, 'c',0, 'e',0, 'G',0, + 'U',0, 'I',0, 'D',0, 0,0, + // ======================================== + // 40 (0x28) + + 0x4E, 0x00, 0x00, 0x00, // 10 + PNL dwPropertyDataLength 4 DWORD Length of the buffer holding the property data + // ======================================== + // 4 + // 14 + PNL bPropertyData PDL Format-dependent Property data + '{',0, 'E',0, 'A',0, '0',0, 'B',0, 'D',0, '5',0, 'C',0, + '3',0, '-',0, '5',0, '0',0, 'F',0, '3',0, '-',0, '4',0, + '8',0, '8',0, '8',0, '-',0, '8',0, '4',0, 'B',0, '4',0, + '-',0, '7',0, '4',0, 'E',0, '5',0, '0',0, 'E',0, '1',0, + '6',0, '4',0, '9',0, 'D',0, 'B',0, '}',0, 0 ,0, + // ======================================== + // 78 (0x4E) +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + 0x3E, 0x00, 0x00, 0x00, // 0 dwSize 0x00000030 (62 bytes) + 0x01, 0x00, 0x00, 0x00, // 4 dwPropertyDataType 0x00000001 (Unicode string) + 0x0C, 0x00, // 8 wPropertyNameLength 0x000C (12 bytes) + // ======================================== + // 10 + 'L',0, 'a',0, 'b',0, 'e',0, 'l',0, 0,0, + // 10 bPropertyName “Label” + // ======================================== + // 12 + 0x24, 0x00, 0x00, 0x00, // 22 dwPropertyDataLength 0x00000016 (36 bytes) + // ======================================== + // 4 + 'W',0, 'i',0, 'n',0, 'U',0, 'S',0, 'B',0, 'C',0, 'o',0, 'm',0, 'm',0, ' ',0, 'd',0, 'e',0, 'v',0, 'i',0, 'c',0, 'e',0, 0,0 + // 26 bPropertyData “WinUSBComm Device” + // ======================================== + // 36 + +}; + + + +static uint8_t USBD_WinUSBComm_GetMSExtendedCompatIDOSDescriptor (USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req) +{ + switch (req->wIndex) + { + case 0x04: + USBD_CtlSendData (pdev, USBD_WinUSBComm_Extended_Compat_ID_OS_Desc, req->wLength); + break; + default: + USBD_CtlError(pdev , req); + return USBD_FAIL; + } + return USBD_OK; +} +static uint8_t USBD_WinUSBComm_GetMSExtendedPropertiesOSDescriptor (USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req) +{ + uint8_t byInterfaceIndex = (uint8_t)req->wValue; + if ( req->wIndex != 0x05 ) + { + USBD_CtlError(pdev , req); + return USBD_FAIL; + } + switch ( byInterfaceIndex ) + { + case 0: +#if NUM_INTERFACES == 2 + case 1: +#endif + USBD_CtlSendData (pdev, USBD_WinUSBComm_Extended_Properties_OS_Desc, req->wLength); + break; + default: + USBD_CtlError(pdev , req); + return USBD_FAIL; + } + return USBD_OK; +} +static uint8_t USBD_WinUSBComm_SetupVendorDevice(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req) +{ + USBD_CtlError(pdev , req); + return USBD_FAIL; +} +static uint8_t USBD_WinUSBComm_SetupVendorInterface(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req) +{ + USBD_CtlError(pdev , req); + // TODO: check if this is important + return USBD_FAIL; +} +static uint8_t USBD_WinUSBComm_SetupVendor(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req) +{ + switch ( req->bmRequest & USB_REQ_RECIPIENT_MASK ) + { + case USB_REQ_RECIPIENT_DEVICE: + return ( MS_VendorCode == req->bRequest ) ? USBD_WinUSBComm_GetMSExtendedCompatIDOSDescriptor(pdev, req) : USBD_WinUSBComm_SetupVendorDevice(pdev, req); + case USB_REQ_RECIPIENT_INTERFACE: + return ( MS_VendorCode == req->bRequest ) ? USBD_WinUSBComm_GetMSExtendedPropertiesOSDescriptor(pdev, req) : USBD_WinUSBComm_SetupVendorInterface(pdev, req); + case USB_REQ_RECIPIENT_ENDPOINT: + // fall through + default: + break; + } + USBD_CtlError(pdev , req); + return USBD_FAIL; +} + /** * @} */ diff --git a/Firmware/Board/v3/Src/usbd_conf.c b/Firmware/Board/v3/Src/usbd_conf.c index f3eb88a9..1b73dfa9 100644 --- a/Firmware/Board/v3/Src/usbd_conf.c +++ b/Firmware/Board/v3/Src/usbd_conf.c @@ -154,6 +154,23 @@ void HAL_PCD_MspDeInit(PCD_HandleTypeDef* pcdHandle) */ void HAL_PCD_SetupStageCallback(PCD_HandleTypeDef *hpcd) { + USBD_StatusTypeDef ret = USBD_OK; + USBD_HandleTypeDef *pdev = hpcd->pData; + USBD_SetupReqTypedef *req = &pdev->request; + USBD_ParseSetupRequest(req, (uint8_t *)hpcd->Setup); + if ( ( USB_REQ_TYPE_VENDOR == (req->bmRequest & USB_REQ_TYPE_MASK) ) && ( MS_VendorCode == req->bRequest ) ) + { + pdev->ep0_state = USBD_EP0_SETUP; + pdev->ep0_data_len = pdev->request.wLength; + + ret = pdev->pClass->Setup(pdev, req); + + if( (req->wLength == 0) && (ret == USBD_OK) ) + { + USBD_CtlSendStatus(pdev); + } + return; + } USBD_LL_SetupStage((USBD_HandleTypeDef*)hpcd->pData, (uint8_t *)hpcd->Setup); } From d69f8312e06ffcdcfe605008adee35437eddb5d6 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 14 May 2018 14:53:30 -0700 Subject: [PATCH 199/215] make the device a composite device --- Firmware/Board/v3/Inc/usbd_cdc_if.h | 2 +- Firmware/Board/v3/Inc/usbd_desc.h | 2 + .../Class/CDC/Inc/usbd_cdc.h | 12 +- .../Class/CDC/Src/usbd_cdc.c | 410 +++++------------- .../Core/Inc/usbd_def.h | 4 +- Firmware/Board/v3/Src/usbd_cdc_if.c | 10 +- Firmware/Board/v3/Src/usbd_conf.c | 5 +- Firmware/Board/v3/Src/usbd_desc.c | 51 ++- Firmware/communication/interface_usb.cpp | 10 +- Firmware/communication/interface_usb.h | 2 +- tools/odrive/usbbulk_transport.py | 29 +- 11 files changed, 210 insertions(+), 327 deletions(-) diff --git a/Firmware/Board/v3/Inc/usbd_cdc_if.h b/Firmware/Board/v3/Inc/usbd_cdc_if.h index c15e8b91..ed1d6705 100644 --- a/Firmware/Board/v3/Inc/usbd_cdc_if.h +++ b/Firmware/Board/v3/Inc/usbd_cdc_if.h @@ -132,7 +132,7 @@ extern USBD_CDC_ItfTypeDef USBD_Interface_fops_FS; * @{ */ -uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len); +uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len, uint8_t endpoint_pair); /* USER CODE BEGIN EXPORTED_FUNCTIONS */ /* USER CODE END EXPORTED_FUNCTIONS */ diff --git a/Firmware/Board/v3/Inc/usbd_desc.h b/Firmware/Board/v3/Inc/usbd_desc.h index d791d3d4..2a74de31 100644 --- a/Firmware/Board/v3/Inc/usbd_desc.h +++ b/Firmware/Board/v3/Inc/usbd_desc.h @@ -133,6 +133,8 @@ extern USBD_DescriptorsTypeDef FS_Desc; /* USER CODE BEGIN EXPORTED_FUNCTIONS */ +uint8_t * USBD_UsrStrDescriptor(struct _USBD_HandleTypeDef *pdev, uint8_t index, uint16_t *length); + /* USER CODE END EXPORTED_FUNCTIONS */ /** diff --git a/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc/usbd_cdc.h b/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc/usbd_cdc.h index d88fc069..3bb73c6e 100644 --- a/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc/usbd_cdc.h +++ b/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Inc/usbd_cdc.h @@ -52,13 +52,15 @@ #define CDC_IN_EP 0x81 /* EP1 for data IN */ #define CDC_OUT_EP 0x01 /* EP1 for data OUT */ #define CDC_CMD_EP 0x82 /* EP2 for CDC commands */ +#define ODRIVE_IN_EP 0x83 /* EP3 IN: ODrive device TX endpoint */ +#define ODRIVE_OUT_EP 0x03 /* EP3 OUT: ODrive device RX endpoint */ /* CDC Endpoints parameters: you can fine tune these values depending on the needed baudrates and performance. */ -#define CDC_DATA_HS_MAX_PACKET_SIZE 512 /* Endpoint IN & OUT Packet size */ +#define CDC_DATA_HS_MAX_PACKET_SIZE 64 /* Endpoint IN & OUT Packet size */ #define CDC_DATA_FS_MAX_PACKET_SIZE 64 /* Endpoint IN & OUT Packet size */ #define CDC_CMD_PACKET_SIZE 8 /* Control Endpoint Packet size */ -#define USB_CDC_CONFIG_DESC_SIZ (67 - 19) +#define USB_CDC_CONFIG_DESC_SIZ (67 + 39) #define CDC_DATA_HS_IN_PACKET_SIZE CDC_DATA_HS_MAX_PACKET_SIZE #define CDC_DATA_HS_OUT_PACKET_SIZE CDC_DATA_HS_MAX_PACKET_SIZE @@ -103,7 +105,7 @@ typedef struct _USBD_CDC_Itf int8_t (* Init) (void); int8_t (* DeInit) (void); int8_t (* Control) (uint8_t, uint8_t * , uint16_t); - int8_t (* Receive) (uint8_t *, uint32_t *); + int8_t (* Receive) (uint8_t *, uint32_t *, uint8_t); }USBD_CDC_ItfTypeDef; @@ -156,9 +158,9 @@ uint8_t USBD_CDC_SetTxBuffer (USBD_HandleTypeDef *pdev, uint8_t USBD_CDC_SetRxBuffer (USBD_HandleTypeDef *pdev, uint8_t *pbuff); -uint8_t USBD_CDC_ReceivePacket (USBD_HandleTypeDef *pdev); +uint8_t USBD_CDC_ReceivePacket (USBD_HandleTypeDef *pdev, uint8_t endpoint_pair); -uint8_t USBD_CDC_TransmitPacket (USBD_HandleTypeDef *pdev); +uint8_t USBD_CDC_TransmitPacket (USBD_HandleTypeDef *pdev, uint8_t endpoint_pair); /** * @} */ diff --git a/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c b/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c index d1465831..2bc01513 100644 --- a/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c +++ b/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Class/CDC/Src/usbd_cdc.c @@ -133,7 +133,7 @@ static uint8_t *USBD_CDC_GetOtherSpeedCfgDesc (uint16_t *length); uint8_t *USBD_CDC_GetDeviceQualifierDescriptor (uint16_t *length); static uint8_t USBD_WinUSBComm_SetupVendor(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req); -static uint8_t * USBD_WinUSBComm_GetUsrStrDescriptor(struct _USBD_HandleTypeDef *pdev, uint8_t index, uint16_t *length); +//static uint8_t * USBD_GetUsrStrDescriptor(struct _USBD_HandleTypeDef *pdev, uint8_t index, uint16_t *length); /* USB Standard Device Descriptor */ __ALIGN_BEGIN static uint8_t USBD_CDC_DeviceQualifierDesc[USB_LEN_DEV_QUALIFIER_DESC] __ALIGN_END = @@ -176,25 +176,37 @@ USBD_ClassTypeDef USBD_CDC = USBD_CDC_GetFSCfgDesc, USBD_CDC_GetOtherSpeedCfgDesc, USBD_CDC_GetDeviceQualifierDescriptor, - USBD_WinUSBComm_GetUsrStrDescriptor + USBD_UsrStrDescriptor }; /* USB CDC device Configuration Descriptor */ -__ALIGN_BEGIN uint8_t USBD_CDC_CfgHSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END = +__ALIGN_BEGIN uint8_t USBD_CDC_CfgDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END = { /*Configuration Descriptor*/ 0x09, /* bLength: Configuration Descriptor size */ USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */ USB_CDC_CONFIG_DESC_SIZ, /* wTotalLength:no of returned bytes */ 0x00, - 0x02, /* bNumInterfaces: 2 interface */ + 0x03, /* bNumInterfaces: 3 interfaces (2 for CDC, 1 custom) */ 0x01, /* bConfigurationValue: Configuration value */ 0x00, /* iConfiguration: Index of string descriptor describing the configuration */ 0xC0, /* bmAttributes: self powered */ 0x32, /* MaxPower 0 mA */ + + /////////////////////////////////////////////////////////////////////////////// + + /* Interface Association Descriptor: CDC device (virtual com port) */ + 0x08, /* bLength: IAD size */ + 0x0B, /* bDescriptorType: Interface Association Descriptor */ + 0x00, /* bFirstInterface */ + 0x02, /* bInterfaceCount */ + 0x02, /* bFunctionClass: Communication Interface Class */ + 0x02, /* bFunctionSubClass: Abstract Control Model */ + 0x01, /* bFunctionProtocol: Common AT commands */ + 0x00, /* iFunction */ /*---------------------------------------------------------------------------*/ - + /*Interface Descriptor */ 0x09, /* bLength: Interface Descriptor size */ USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */ @@ -202,11 +214,11 @@ __ALIGN_BEGIN uint8_t USBD_CDC_CfgHSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END = 0x00, /* bInterfaceNumber: Number of Interface */ 0x00, /* bAlternateSetting: Alternate setting */ 0x01, /* bNumEndpoints: One endpoints used */ - 0x00, /* bInterfaceClass: Communication Interface Class */ - 0x00, /* bInterfaceSubClass: Abstract Control Model */ - 0x00, /* bInterfaceProtocol: Common AT commands */ + 0x02, /* bInterfaceClass: Communication Interface Class */ + 0x02, /* bInterfaceSubClass: Abstract Control Model */ + 0x01, /* bInterfaceProtocol: Common AT commands */ 0x00, /* iInterface: */ -#if 0 + /*Header Functional Descriptor*/ 0x05, /* bLength: Endpoint Descriptor size */ 0x24, /* bDescriptorType: CS_INTERFACE */ @@ -233,7 +245,7 @@ __ALIGN_BEGIN uint8_t USBD_CDC_CfgHSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END = 0x06, /* bDescriptorSubtype: Union func desc */ 0x00, /* bMasterInterface: Communication class interface */ 0x01, /* bSlaveInterface0: Data Class Interface */ -#endif + /*Endpoint 2 Descriptor*/ 0x07, /* bLength: Endpoint Descriptor size */ USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */ @@ -271,196 +283,52 @@ __ALIGN_BEGIN uint8_t USBD_CDC_CfgHSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END = 0x02, /* bmAttributes: Bulk */ LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), /* wMaxPacketSize: */ HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), - 0x00 /* bInterval: ignore for Bulk transfer */ -} ; + 0x00, /* bInterval: ignore for Bulk transfer */ + /////////////////////////////////////////////////////////////////////////////// + + /* Interface Association Descriptor: custom device */ + 0x08, /* bLength: IAD size */ + 0x0B, /* bDescriptorType: Interface Association Descriptor */ + 0x02, /* bFirstInterface */ + 0x01, /* bInterfaceCount */ + 0x00, /* bFunctionClass: Communication Interface Class */ + 0x00, /* bFunctionSubClass: Abstract Control Model */ + 0x00, /* bFunctionProtocol: Common AT commands */ + 0x06, /* iFunction */ -/* USB CDC device Configuration Descriptor */ -__ALIGN_BEGIN uint8_t USBD_CDC_CfgFSDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END = -{ - /*Configuration Descriptor*/ - 0x09, /* bLength: Configuration Descriptor size */ - USB_DESC_TYPE_CONFIGURATION, /* bDescriptorType: Configuration */ - USB_CDC_CONFIG_DESC_SIZ, /* wTotalLength:no of returned bytes */ - 0x00, - 0x02, /* bNumInterfaces: 2 interface */ - 0x01, /* bConfigurationValue: Configuration value */ - 0x00, /* iConfiguration: Index of string descriptor describing the configuration */ - 0xC0, /* bmAttributes: self powered */ - 0x32, /* MaxPower 0 mA */ - - /*---------------------------------------------------------------------------*/ - - /*Interface Descriptor */ - 0x09, /* bLength: Interface Descriptor size */ - USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */ - /* Interface descriptor type */ - 0x00, /* bInterfaceNumber: Number of Interface */ - 0x00, /* bAlternateSetting: Alternate setting */ - 0x01, /* bNumEndpoints: One endpoints used */ - 0x00, /* bInterfaceClass: Communication Interface Class */ - 0x00, /* bInterfaceSubClass: Abstract Control Model */ - 0x00, /* bInterfaceProtocol: Common AT commands */ - 0x00, /* iInterface: */ -#if 0 - /*Header Functional Descriptor*/ - 0x05, /* bLength: Endpoint Descriptor size */ - 0x24, /* bDescriptorType: CS_INTERFACE */ - 0x00, /* bDescriptorSubtype: Header Func Desc */ - 0x10, /* bcdCDC: spec release number */ - 0x01, - - /*Call Management Functional Descriptor*/ - 0x05, /* bFunctionLength */ - 0x24, /* bDescriptorType: CS_INTERFACE */ - 0x01, /* bDescriptorSubtype: Call Management Func Desc */ - 0x00, /* bmCapabilities: D0+D1 */ - 0x01, /* bDataInterface: 1 */ - - /*ACM Functional Descriptor*/ - 0x04, /* bFunctionLength */ - 0x24, /* bDescriptorType: CS_INTERFACE */ - 0x02, /* bDescriptorSubtype: Abstract Control Management desc */ - 0x02, /* bmCapabilities */ - - /*Union Functional Descriptor*/ - 0x05, /* bFunctionLength */ - 0x24, /* bDescriptorType: CS_INTERFACE */ - 0x06, /* bDescriptorSubtype: Union func desc */ - 0x00, /* bMasterInterface: Communication class interface */ - 0x01, /* bSlaveInterface0: Data Class Interface */ -#endif - /*Endpoint 2 Descriptor*/ - 0x07, /* bLength: Endpoint Descriptor size */ - USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */ - CDC_CMD_EP, /* bEndpointAddress */ - 0x03, /* bmAttributes: Interrupt */ - LOBYTE(CDC_CMD_PACKET_SIZE), /* wMaxPacketSize: */ - HIBYTE(CDC_CMD_PACKET_SIZE), - 0x10, /* bInterval: */ /*---------------------------------------------------------------------------*/ /*Data class interface descriptor*/ 0x09, /* bLength: Endpoint Descriptor size */ USB_DESC_TYPE_INTERFACE, /* bDescriptorType: */ - 0x01, /* bInterfaceNumber: Number of Interface */ + 0x02, /* bInterfaceNumber: Number of Interface */ 0x00, /* bAlternateSetting: Alternate setting */ 0x02, /* bNumEndpoints: Two endpoints used */ - 0x0A, /* bInterfaceClass: CDC */ - 0x00, /* bInterfaceSubClass: */ + 0x00, /* bInterfaceClass: vendor specific */ + 0x01, /* bInterfaceSubClass: ODrive Communication */ 0x00, /* bInterfaceProtocol: */ 0x00, /* iInterface: */ /*Endpoint OUT Descriptor*/ 0x07, /* bLength: Endpoint Descriptor size */ USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */ - CDC_OUT_EP, /* bEndpointAddress */ + ODRIVE_OUT_EP, /* bEndpointAddress */ 0x02, /* bmAttributes: Bulk */ - LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize: */ - HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), + LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), /* wMaxPacketSize: */ + HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), 0x00, /* bInterval: ignore for Bulk transfer */ /*Endpoint IN Descriptor*/ 0x07, /* bLength: Endpoint Descriptor size */ USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */ - CDC_IN_EP, /* bEndpointAddress */ + ODRIVE_IN_EP, /* bEndpointAddress */ 0x02, /* bmAttributes: Bulk */ - LOBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), /* wMaxPacketSize: */ - HIBYTE(CDC_DATA_FS_MAX_PACKET_SIZE), - 0x00 /* bInterval: ignore for Bulk transfer */ + LOBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), /* wMaxPacketSize: */ + HIBYTE(CDC_DATA_HS_MAX_PACKET_SIZE), + 0x00, /* bInterval: ignore for Bulk transfer */ } ; -__ALIGN_BEGIN uint8_t USBD_CDC_OtherSpeedCfgDesc[USB_CDC_CONFIG_DESC_SIZ] __ALIGN_END = -{ - 0x09, /* bLength: Configuation Descriptor size */ - USB_DESC_TYPE_OTHER_SPEED_CONFIGURATION, - USB_CDC_CONFIG_DESC_SIZ, - 0x00, - 0x02, /* bNumInterfaces: 2 interfaces */ - 0x01, /* bConfigurationValue: */ - 0x04, /* iConfiguration: */ - 0xC0, /* bmAttributes: */ - 0x32, /* MaxPower 100 mA */ - - /*Interface Descriptor */ - 0x09, /* bLength: Interface Descriptor size */ - USB_DESC_TYPE_INTERFACE, /* bDescriptorType: Interface */ - /* Interface descriptor type */ - 0x00, /* bInterfaceNumber: Number of Interface */ - 0x00, /* bAlternateSetting: Alternate setting */ - 0x01, /* bNumEndpoints: One endpoints used */ - 0x00, /* bInterfaceClass: Communication Interface Class */ - 0x00, /* bInterfaceSubClass: Abstract Control Model */ - 0x00, /* bInterfaceProtocol: Common AT commands */ - 0x00, /* iInterface: */ -#if 0 - /*Header Functional Descriptor*/ - 0x05, /* bLength: Endpoint Descriptor size */ - 0x24, /* bDescriptorType: CS_INTERFACE */ - 0x00, /* bDescriptorSubtype: Header Func Desc */ - 0x10, /* bcdCDC: spec release number */ - 0x01, - - /*Call Management Functional Descriptor*/ - 0x05, /* bFunctionLength */ - 0x24, /* bDescriptorType: CS_INTERFACE */ - 0x01, /* bDescriptorSubtype: Call Management Func Desc */ - 0x00, /* bmCapabilities: D0+D1 */ - 0x01, /* bDataInterface: 1 */ - - /*ACM Functional Descriptor*/ - 0x04, /* bFunctionLength */ - 0x24, /* bDescriptorType: CS_INTERFACE */ - 0x02, /* bDescriptorSubtype: Abstract Control Management desc */ - 0x02, /* bmCapabilities */ - - /*Union Functional Descriptor*/ - 0x05, /* bFunctionLength */ - 0x24, /* bDescriptorType: CS_INTERFACE */ - 0x06, /* bDescriptorSubtype: Union func desc */ - 0x00, /* bMasterInterface: Communication class interface */ - 0x01, /* bSlaveInterface0: Data Class Interface */ -#endif - /*Endpoint 2 Descriptor*/ - 0x07, /* bLength: Endpoint Descriptor size */ - USB_DESC_TYPE_ENDPOINT , /* bDescriptorType: Endpoint */ - CDC_CMD_EP, /* bEndpointAddress */ - 0x03, /* bmAttributes: Interrupt */ - LOBYTE(CDC_CMD_PACKET_SIZE), /* wMaxPacketSize: */ - HIBYTE(CDC_CMD_PACKET_SIZE), - 0xFF, /* bInterval: */ - - /*---------------------------------------------------------------------------*/ - - /*Data class interface descriptor*/ - 0x09, /* bLength: Endpoint Descriptor size */ - USB_DESC_TYPE_INTERFACE, /* bDescriptorType: */ - 0x01, /* bInterfaceNumber: Number of Interface */ - 0x00, /* bAlternateSetting: Alternate setting */ - 0x02, /* bNumEndpoints: Two endpoints used */ - 0x0A, /* bInterfaceClass: CDC */ - 0x00, /* bInterfaceSubClass: */ - 0x00, /* bInterfaceProtocol: */ - 0x00, /* iInterface: */ - - /*Endpoint OUT Descriptor*/ - 0x07, /* bLength: Endpoint Descriptor size */ - USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */ - CDC_OUT_EP, /* bEndpointAddress */ - 0x02, /* bmAttributes: Bulk */ - 0x40, /* wMaxPacketSize: */ - 0x00, - 0x00, /* bInterval: ignore for Bulk transfer */ - - /*Endpoint IN Descriptor*/ - 0x07, /* bLength: Endpoint Descriptor size */ - USB_DESC_TYPE_ENDPOINT, /* bDescriptorType: Endpoint */ - CDC_IN_EP, /* bEndpointAddress */ - 0x02, /* bmAttributes: Bulk */ - 0x40, /* wMaxPacketSize: */ - 0x00, - 0x00 /* bInterval */ -}; /** * @} @@ -512,6 +380,19 @@ static uint8_t USBD_CDC_Init (USBD_HandleTypeDef *pdev, USBD_EP_TYPE_BULK, CDC_DATA_FS_OUT_PACKET_SIZE); } + + /* Open ODrive IN endpoint */ + USBD_LL_OpenEP(pdev, + ODRIVE_IN_EP, + USBD_EP_TYPE_BULK, + pdev->dev_speed == USBD_SPEED_HIGH ? CDC_DATA_HS_IN_PACKET_SIZE : CDC_DATA_FS_IN_PACKET_SIZE); + + /* Open ODrive OUT endpoint */ + USBD_LL_OpenEP(pdev, + ODRIVE_OUT_EP, + USBD_EP_TYPE_BULK, + pdev->dev_speed == USBD_SPEED_HIGH ? CDC_DATA_HS_OUT_PACKET_SIZE : CDC_DATA_FS_OUT_PACKET_SIZE); + /* Open Command IN EP */ USBD_LL_OpenEP(pdev, CDC_CMD_EP, @@ -553,7 +434,11 @@ static uint8_t USBD_CDC_Init (USBD_HandleTypeDef *pdev, CDC_DATA_FS_OUT_PACKET_SIZE); } - + /* Prepare ODrive Out endpoint to receive next packet */ + USBD_LL_PrepareReceive(pdev, + ODRIVE_OUT_EP, + hcdc->RxBuffer, + CDC_DATA_FS_OUT_PACKET_SIZE); } return ret; } @@ -570,17 +455,25 @@ static uint8_t USBD_CDC_DeInit (USBD_HandleTypeDef *pdev, { uint8_t ret = 0; - /* Open EP IN */ + /* Close EP IN */ USBD_LL_CloseEP(pdev, CDC_IN_EP); - /* Open EP OUT */ + /* Close EP OUT */ USBD_LL_CloseEP(pdev, CDC_OUT_EP); - /* Open Command IN EP */ + /* Close Command IN EP */ USBD_LL_CloseEP(pdev, CDC_CMD_EP); + + /* Close EP IN */ + USBD_LL_CloseEP(pdev, + ODRIVE_IN_EP); + + /* Close EP OUT */ + USBD_LL_CloseEP(pdev, + ODRIVE_OUT_EP); /* DeInit physical Interface components */ @@ -704,7 +597,7 @@ static uint8_t USBD_CDC_DataOut (USBD_HandleTypeDef *pdev, uint8_t epnum) NAKed till the end of the application Xfer */ if(pdev->pClassData != NULL) { - ((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Receive(hcdc->RxBuffer, &hcdc->RxLength); + ((USBD_CDC_ItfTypeDef *)pdev->pUserData)->Receive(hcdc->RxBuffer, &hcdc->RxLength, epnum); return USBD_OK; } @@ -747,8 +640,8 @@ static uint8_t USBD_CDC_EP0_RxReady (USBD_HandleTypeDef *pdev) */ static uint8_t *USBD_CDC_GetFSCfgDesc (uint16_t *length) { - *length = sizeof (USBD_CDC_CfgFSDesc); - return USBD_CDC_CfgFSDesc; + *length = sizeof (USBD_CDC_CfgDesc); + return USBD_CDC_CfgDesc; } /** @@ -760,8 +653,8 @@ static uint8_t *USBD_CDC_GetFSCfgDesc (uint16_t *length) */ static uint8_t *USBD_CDC_GetHSCfgDesc (uint16_t *length) { - *length = sizeof (USBD_CDC_CfgHSDesc); - return USBD_CDC_CfgHSDesc; + *length = sizeof (USBD_CDC_CfgDesc); + return USBD_CDC_CfgDesc; } /** @@ -773,8 +666,8 @@ static uint8_t *USBD_CDC_GetHSCfgDesc (uint16_t *length) */ static uint8_t *USBD_CDC_GetOtherSpeedCfgDesc (uint16_t *length) { - *length = sizeof (USBD_CDC_OtherSpeedCfgDesc); - return USBD_CDC_OtherSpeedCfgDesc; + *length = sizeof (USBD_CDC_CfgDesc); + return USBD_CDC_CfgDesc; } /** @@ -851,7 +744,7 @@ uint8_t USBD_CDC_SetRxBuffer (USBD_HandleTypeDef *pdev, * @param epnum: endpoint number * @retval status */ -uint8_t USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev) +uint8_t USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev, uint8_t endpoint_pair) { USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*) pdev->pClassData; @@ -862,11 +755,19 @@ uint8_t USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev) /* Tx Transfer in progress */ hcdc->TxState = 1; - /* Transmit next packet */ - USBD_LL_Transmit(pdev, - CDC_IN_EP, - hcdc->TxBuffer, - hcdc->TxLength); + //endpoint_pair = 1; + if (endpoint_pair == 1) { + /* Transmit next packet */ + USBD_LL_Transmit(pdev, + CDC_IN_EP, + hcdc->TxBuffer, + hcdc->TxLength); + } else if (endpoint_pair == 3) { + USBD_LL_Transmit(pdev, + ODRIVE_IN_EP, + hcdc->TxBuffer, + hcdc->TxLength); + } return USBD_OK; } @@ -888,29 +789,30 @@ uint8_t USBD_CDC_TransmitPacket(USBD_HandleTypeDef *pdev) * @param pdev: device instance * @retval status */ -uint8_t USBD_CDC_ReceivePacket(USBD_HandleTypeDef *pdev) +uint8_t USBD_CDC_ReceivePacket(USBD_HandleTypeDef *pdev, uint8_t endpoint_pair) { USBD_CDC_HandleTypeDef *hcdc = (USBD_CDC_HandleTypeDef*) pdev->pClassData; /* Suspend or Resume USB Out process */ if(pdev->pClassData != NULL) { - if(pdev->dev_speed == USBD_SPEED_HIGH ) + if (endpoint_pair == CDC_OUT_EP) { /* Prepare Out endpoint to receive next packet */ USBD_LL_PrepareReceive(pdev, CDC_OUT_EP, hcdc->RxBuffer, - CDC_DATA_HS_OUT_PACKET_SIZE); + pdev->dev_speed == USBD_SPEED_HIGH ? CDC_DATA_HS_OUT_PACKET_SIZE : CDC_DATA_FS_OUT_PACKET_SIZE); } - else + else if (endpoint_pair == ODRIVE_OUT_EP) { - /* Prepare Out endpoint to receive next packet */ + /* Prepare ODrive Out endpoint to receive next packet */ USBD_LL_PrepareReceive(pdev, - CDC_OUT_EP, + ODRIVE_OUT_EP, hcdc->RxBuffer, - CDC_DATA_FS_OUT_PACKET_SIZE); + pdev->dev_speed == USBD_SPEED_HIGH ? CDC_DATA_HS_OUT_PACKET_SIZE : CDC_DATA_FS_OUT_PACKET_SIZE); } + return USBD_OK; } else @@ -919,95 +821,15 @@ uint8_t USBD_CDC_ReceivePacket(USBD_HandleTypeDef *pdev) } } -#if 0 -// Microsoft OS 2.0 Descriptor Set -uint8_t ms_os_20_descriptor_set[0x9E] = { - -0x0A, 0x00, // Descriptor size (10 bytes) -0x00, 0x00, // MS OS 2.0 descriptor set header -0x00, 0x00, 0x03, 0x06, // Windows version (8.1) (0x06030000) -0x9E, 0x00, // Size, MS OS 2.0 descriptor set (158 bytes) - -// Microsoft OS 2.0 compatible ID descriptor - -0x14, 0x00, // Descriptor size (20 bytes) -0x03, 0x00, // MS OS 2.0 compatible ID descriptor -0x57, 0x49, 0x4E, 0x55, 0x53, 0x42, 0x00, 0x00, // WINUSB string -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Sub-compatible ID - -// Registry property descriptor - -0x80, 0x00, // Descriptor size (130 bytes) -0x04, 0x00, // Registry Property descriptor -0x01, 0x00, // Strings are null-terminated Unicode -0x28, 0x00, // Size of Property Name (40 bytes) - -//Property Name ("DeviceInterfaceGUID") - -0x44, 0x00, 0x65, 0x00, 0x76, 0x00, 0x69, 0x00, 0x63, 0x00, 0x65, 0x00, -0x49, 0x00, 0x6E, 0x00, 0x74, 0x00, 0x65, 0x00, 0x72, 0x00, 0x66, 0x00, -0x61, 0x00, 0x63, 0x00, 0x65, 0x00, 0x47, 0x00, 0x55, 0x00, 0x49, 0x00, -0x44, 0x00, 0x00, 0x00, - -0x4E, 0x00, // Size of Property Data (78 bytes) - -// Vendor-defined Property Data: {ecceff35-146c-4ff3-acd9-8f992d09acdd} - -0x7B, 0x00, 0x65, 0x00, 0x63, 0x00, 0x63, 0x00, 0x65, 0x00, 0x66, 0x00, -0x66, 0x00, 0x33, 0x00, 0x35, 0x00, 0x2D, 0x00, 0x31, 0x00, 0x34, 0x00, -0x36, 0x00, 0x33, 0x00, 0x2D, 0x00, 0x34, 0x00, 0x66, 0x00, 0x66, 0x00, -0x33, 0x00, 0x2D, 0x00, 0x61, 0x00, 0x63, 0x00, 0x64, 0x00, 0x39, 0x00, -0x2D, 0x00, 0x38, 0x00, 0x66, 0x00, 0x39, 0x00, 0x39, 0x00, 0x32, 0x00, -0x64, 0x00, 0x30, 0x00, 0x39, 0x00, 0x61, 0x00, 0x63, 0x00, 0x64, 0x00, -0x64, 0x00, 0x7D, 0x00, 0x00, 0x00 -}; -#endif - - -// MS OS String descriptor to tell Windows that it may query for other descriptors -// It's a standard string descriptor. -// Windows will only query for OS descriptors once! -// Delete the information about already queried devices in registry by deleting: -// HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\usbflags\VVVVPPPPRRRR -__ALIGN_BEGIN uint8_t USBD_WinUSBComm_MS_OS_StringDescriptor[] __ALIGN_END = -{ - 0x12, // bLength 1 0x12 Length of the descriptor - 0x03, // bDescriptorType 1 0x03 Descriptor type - // qwSignature 14 ‘MSFT100’ Signature field - 0x4D, 0x00, // 'M' - 0x53, 0x00, // 'S' - 0x46, 0x00, // 'F' - 0x54, 0x00, // 'T' - 0x31, 0x00, // '1' - 0x30, 0x00, // '0' - 0x30, 0x00, // '0' - MS_VendorCode, // bMS_VendorCode 1 Vendor-specific Vendor code - 0x00 // bPad 1 0x00 Pad field -}; - - -/** -* @brief GetUsrStrDescriptor -* return non standard string descriptor (OS String Descriptor) -* @param pdev: device instance -* @param index : descriptor index (0xEE for MS OS String Descriptor) -* @param length : pointer data length -* @retval pointer to descriptor buffer +/* WinUSB support ------------------------------------------------------------*/ +/* +* This section tells Windows that it should automatically load the WinUSB driver +* for the device (more specifically, interface 2 because it's a composite device). +* This allows for driverless communication with the device. */ -static uint8_t * USBD_WinUSBComm_GetUsrStrDescriptor(struct _USBD_HandleTypeDef *pdev, uint8_t index, uint16_t *length) -{ - *length = 0; - if ( 0xEE == index ) - { - *length = sizeof (USBD_WinUSBComm_MS_OS_StringDescriptor); - return USBD_WinUSBComm_MS_OS_StringDescriptor; - } - return NULL; -} - -#define NUM_INTERFACES 2 +#define NUM_INTERFACES 1 #if NUM_INTERFACES == 2 #define USB_WINUSBCOMM_COMPAT_ID_OS_DESC_SIZ (16 + 24 + 24) @@ -1025,7 +847,7 @@ __ALIGN_BEGIN uint8_t USBD_WinUSBComm_Extended_Compat_ID_OS_Desc[USB_WINUSBCOMM_ USB_WINUSBCOMM_COMPAT_ID_OS_DESC_SIZ, 0, 0, 0, // 0 dwLength 4 DWORD The length, in bytes, of the complete extended compat ID descriptor 0x00, 0x01, // 4 bcdVersion 2 BCD The descriptor’s version number, in binary coded decimal (BCD) format 0x04, 0x00, // 6 wIndex 2 WORD An index that identifies the particular OS feature descriptor - 2, // 8 bCount 1 BYTE The number of custom property sections + NUM_INTERFACES, // 8 bCount 1 BYTE The number of custom property sections 0, 0, 0, 0, 0, 0, 0, // 9 RESERVED 7 BYTEs Reserved // ===================== // 16 @@ -1033,7 +855,7 @@ __ALIGN_BEGIN uint8_t USBD_WinUSBComm_Extended_Compat_ID_OS_Desc[USB_WINUSBCOMM_ // +-- Offset from function section start // | +-- Size // v v - 0, // 0 bFirstInterfaceNumber 1 BYTE The interface or function number + 2, // 0 bFirstInterfaceNumber 1 BYTE The interface or function number 0, // 1 RESERVED 1 BYTE Reserved 0x57, 0x49, 0x4E, 0x55, 0x53, 0x42, 0x00, 0x00, // 2 compatibleID 8 BYTEs The function’s compatible ID ("WINUSB") 0, 0, 0, 0, 0, 0, 0, 0, // 10 subCompatibleID 8 BYTEs The function’s subcompatible ID @@ -1044,7 +866,7 @@ __ALIGN_BEGIN uint8_t USBD_WinUSBComm_Extended_Compat_ID_OS_Desc[USB_WINUSBCOMM_ // +-- Offset from function section start // | +-- Size // v v - 1, // 0 bFirstInterfaceNumber 1 BYTE The interface or function number + 2, // 0 bFirstInterfaceNumber 1 BYTE The interface or function number 0, // 1 RESERVED 1 BYTE Reserved 0x57, 0x49, 0x4E, 0x55, 0x53, 0x42, 0x00, 0x00, // 2 compatibleID 8 BYTEs The function’s compatible ID ("WINUSB") 0, 0, 0, 0, 0, 0, 0, 0, // 10 subCompatibleID 8 BYTEs The function’s subcompatible ID @@ -1059,9 +881,9 @@ __ALIGN_BEGIN uint8_t USBD_WinUSBComm_Extended_Compat_ID_OS_Desc[USB_WINUSBCOMM_ // HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USB\VID_xxxx&PID_xxxx\sssssssss\Device Parameters // Use USBDeview or similar to uninstall -__ALIGN_BEGIN uint8_t USBD_WinUSBComm_Extended_Properties_OS_Desc[0xCC] __ALIGN_END = +__ALIGN_BEGIN uint8_t USBD_WinUSBComm_Extended_Properties_OS_Desc[0xB6] __ALIGN_END = { - 0xCC, 0x00, 0x00, 0x00, // 0 dwLength 4 DWORD The length, in bytes, of the complete extended properties descriptor + 0xB6, 0x00, 0x00, 0x00, // 0 dwLength 4 DWORD The length, in bytes, of the complete extended properties descriptor 0x00, 0x01, // 4 bcdVersion 2 BCD The descriptor’s version number, in binary coded decimal (BCD) format 0x05, 0x00, // 6 wIndex 2 WORD The index for extended properties OS descriptors 0x02, 0x00, // 8 wCount 2 WORD The number of custom property sections that follow the header section @@ -1104,10 +926,10 @@ __ALIGN_BEGIN uint8_t USBD_WinUSBComm_Extended_Properties_OS_Desc[0xCC] __ALIGN 0x24, 0x00, 0x00, 0x00, // 22 dwPropertyDataLength 0x00000016 (36 bytes) // ======================================== // 4 - 'W',0, 'i',0, 'n',0, 'U',0, 'S',0, 'B',0, 'C',0, 'o',0, 'm',0, 'm',0, ' ',0, 'd',0, 'e',0, 'v',0, 'i',0, 'c',0, 'e',0, 0,0 - // 26 bPropertyData “WinUSBComm Device” + 'O',0, 'D',0, 'r',0, 'i',0, 'v',0, 'e',0, 0,0 + // 26 bPropertyData “ODrive” // ======================================== - // 36 + // 14 }; diff --git a/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Core/Inc/usbd_def.h b/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Core/Inc/usbd_def.h index 8fbe81e4..f259b51d 100644 --- a/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Core/Inc/usbd_def.h +++ b/Firmware/Board/v3/Middlewares/ST/STM32_USB_Device_Library/Core/Inc/usbd_def.h @@ -68,7 +68,9 @@ #define USBD_IDX_PRODUCT_STR 0x02 #define USBD_IDX_SERIAL_STR 0x03 #define USBD_IDX_CONFIG_STR 0x04 -#define USBD_IDX_INTERFACE_STR 0x05 +#define USBD_IDX_INTERFACE_STR 0x05 +#define USBD_IDX_ODRIVE_INTF_STR 0x06 +#define USBD_IDX_MICROSOFT_DESC_STR 0xEE #define USB_REQ_TYPE_STANDARD 0x00 #define USB_REQ_TYPE_CLASS 0x20 diff --git a/Firmware/Board/v3/Src/usbd_cdc_if.c b/Firmware/Board/v3/Src/usbd_cdc_if.c index 1a9c43c4..77e70b2c 100644 --- a/Firmware/Board/v3/Src/usbd_cdc_if.c +++ b/Firmware/Board/v3/Src/usbd_cdc_if.c @@ -151,7 +151,7 @@ extern USBD_HandleTypeDef hUsbDeviceFS; static int8_t CDC_Init_FS(void); static int8_t CDC_DeInit_FS(void); static int8_t CDC_Control_FS(uint8_t cmd, uint8_t* pbuf, uint16_t length); -static int8_t CDC_Receive_FS(uint8_t* pbuf, uint32_t *Len); +static int8_t CDC_Receive_FS(uint8_t* pbuf, uint32_t *Len, uint8_t endpoint_pair); /* USER CODE BEGIN PRIVATE_FUNCTIONS_DECLARATION */ /* USER CODE END PRIVATE_FUNCTIONS_DECLARATION */ @@ -287,10 +287,10 @@ static int8_t CDC_Control_FS(uint8_t cmd, uint8_t* pbuf, uint16_t length) * @param Len: Number of data received (in bytes) * @retval Result of the operation: USBD_OK if all operations are OK else USBD_FAIL */ -static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len) +static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len, uint8_t endpoint_pair) { /* USER CODE BEGIN 6 */ - usb_process_packet(Buf, *Len); + usb_process_packet(Buf, *Len, endpoint_pair); return (USBD_OK); /* USER CODE END 6 */ @@ -307,7 +307,7 @@ static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len) * @param Len: Number of data to be sent (in bytes) * @retval USBD_OK if all operations are OK else USBD_FAIL or USBD_BUSY */ -uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len) +uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len, uint8_t endpoint_pair) { uint8_t result = USBD_OK; /* USER CODE BEGIN 7 */ @@ -323,7 +323,7 @@ uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len) memcpy(UserTxBufferFS, Buf, Len); // Update Len USBD_CDC_SetTxBuffer(&hUsbDeviceFS, UserTxBufferFS, Len); - result = USBD_CDC_TransmitPacket(&hUsbDeviceFS); + result = USBD_CDC_TransmitPacket(&hUsbDeviceFS, endpoint_pair); /* USER CODE END 7 */ return result; } diff --git a/Firmware/Board/v3/Src/usbd_conf.c b/Firmware/Board/v3/Src/usbd_conf.c index 1b73dfa9..2d66d4a1 100644 --- a/Firmware/Board/v3/Src/usbd_conf.c +++ b/Firmware/Board/v3/Src/usbd_conf.c @@ -329,7 +329,7 @@ USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev) pdev->pData = &hpcd_USB_OTG_FS; hpcd_USB_OTG_FS.Instance = USB_OTG_FS; - hpcd_USB_OTG_FS.Init.dev_endpoints = 4; + hpcd_USB_OTG_FS.Init.dev_endpoints = 6; hpcd_USB_OTG_FS.Init.speed = PCD_SPEED_FULL; hpcd_USB_OTG_FS.Init.dma_enable = DISABLE; hpcd_USB_OTG_FS.Init.ep0_mps = DEP0CTL_MPS_64; @@ -346,7 +346,8 @@ USBD_StatusTypeDef USBD_LL_Init(USBD_HandleTypeDef *pdev) HAL_PCDEx_SetRxFiFo(&hpcd_USB_OTG_FS, 0x80); HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 0, 0x40); - HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 1, 0x80); + HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 1, 0x40); // CDC IN endpoint + HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 3, 0x40); // ODrive IN endpoint } return USBD_OK; } diff --git a/Firmware/Board/v3/Src/usbd_desc.c b/Firmware/Board/v3/Src/usbd_desc.c index b8d6efac..45cbf1f6 100644 --- a/Firmware/Board/v3/Src/usbd_desc.c +++ b/Firmware/Board/v3/Src/usbd_desc.c @@ -114,6 +114,48 @@ /* USER CODE BEGIN 0 */ +// MS OS String descriptor to tell Windows that it may query for other descriptors +// It's a standard string descriptor. +// Windows will only query for OS descriptors once! +// Delete the information about already queried devices in registry by deleting: +// HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\usbflags\VVVVPPPPRRRR +__ALIGN_BEGIN uint8_t USBD_MS_OS_StringDescriptor[] __ALIGN_END = +{ + 0x12, // bLength 1 0x12 Length of the descriptor + 0x03, // bDescriptorType 1 0x03 Descriptor type + // qwSignature 14 ‘MSFT100’ Signature field + 0x4D, 0x00, // 'M' + 0x53, 0x00, // 'S' + 0x46, 0x00, // 'F' + 0x54, 0x00, // 'T' + 0x31, 0x00, // '1' + 0x30, 0x00, // '0' + 0x30, 0x00, // '0' + MS_VendorCode, // bMS_VendorCode 1 Vendor-specific Vendor code + 0x00 // bPad 1 0x00 Pad field +}; + +/** +* @brief UsrStrDescriptor +* return non standard string descriptor +* @param pdev: device instance +* @param index : descriptor index (0xEE for MS OS String Descriptor) +* @param length : pointer data length +* @retval pointer to descriptor buffer +*/ +uint8_t * USBD_UsrStrDescriptor(struct _USBD_HandleTypeDef *pdev, uint8_t index, uint16_t *length) +{ + *length = 0; + if (USBD_IDX_MICROSOFT_DESC_STR == index) { + *length = sizeof (USBD_MS_OS_StringDescriptor); + return USBD_MS_OS_StringDescriptor; + } else if (USBD_IDX_ODRIVE_INTF_STR == index) { + USBD_GetString((uint8_t *)"ODrive Interface", USBD_StrDesc, length); + return USBD_StrDesc; + } + return NULL; +} + /* USER CODE END 0 */ /** @defgroup USBD_DESC_Private_Macros USBD_DESC_Private_Macros @@ -189,16 +231,17 @@ __ALIGN_BEGIN uint8_t USBD_FS_DeviceDesc[USB_LEN_DEV_DESC] __ALIGN_END = 0x00, /*bcdUSB */ #endif /* (USBD_LPM_ENABLED == 1) */ 0x02, - 0x00, /*bDeviceClass*/ - 0x00, /*bDeviceSubClass*/ - 0x00, /*bDeviceProtocol*/ + // Notify OS that this is a composite device + 0xEF, /*bDeviceClass*/ + 0x02, /*bDeviceSubClass*/ + 0x01, /*bDeviceProtocol*/ USB_MAX_EP0_SIZE, /*bMaxPacketSize*/ LOBYTE(USBD_VID), /*idVendor*/ HIBYTE(USBD_VID), /*idVendor*/ LOBYTE(USBD_PID_FS), /*idProduct*/ HIBYTE(USBD_PID_FS), /*idProduct*/ 0x00, /*bcdDevice rel. 2.00*/ - 0x02, + 0x03, /* bNumInterfaces */ USBD_IDX_MFC_STR, /*Index of manufacturer string*/ USBD_IDX_PRODUCT_STR, /*Index of product string*/ USBD_IDX_SERIAL_STR, /*Index of serial number string*/ diff --git a/Firmware/communication/interface_usb.cpp b/Firmware/communication/interface_usb.cpp index 28891a82..57b52d3b 100644 --- a/Firmware/communication/interface_usb.cpp +++ b/Firmware/communication/interface_usb.cpp @@ -12,6 +12,7 @@ static uint8_t* usb_buf; static uint32_t usb_len; +static uint8_t active_endpoint_pair; // FIXME: the stdlib doesn't know about CMSIS threads, so this is just a global variable static thread_local uint32_t deadline_ms = 0; @@ -37,12 +38,12 @@ public: // transmit packet uint8_t status = CDC_Transmit_FS( const_cast(buffer) /* casting this const away is safe because... - well... it's not actually. Stupid STM. */, length); + well... it's not actually. Stupid STM. */, length, active_endpoint_pair); if (status != USBD_OK) { osSemaphoreRelease(sem_usb_tx); return -1; } - usb_stats_.tx_cnt = 0; + usb_stats_.tx_cnt++; return 0; } } usb_packet_output; @@ -94,16 +95,17 @@ static void usb_server_thread(void * ctx) { #elif defined(USB_PROTOCOL_ASCII) ASCII_protocol_parse_stream(usb_buf, usb_len, usb_stream_output); #endif - USBD_CDC_ReceivePacket(&hUsbDeviceFS); // Allow next packet + USBD_CDC_ReceivePacket(&hUsbDeviceFS, active_endpoint_pair); // Allow next packet } } } // Called from CDC_Receive_FS callback function, this allows the communication // thread to handle the incoming data -void usb_process_packet(uint8_t *buf, uint32_t len) { +void usb_process_packet(uint8_t *buf, uint32_t len, uint8_t endpoint_pair) { usb_buf = buf; usb_len = len; + active_endpoint_pair = endpoint_pair; osSemaphoreRelease(sem_usb_rx); } diff --git a/Firmware/communication/interface_usb.h b/Firmware/communication/interface_usb.h index a56bca36..a4f1c9a4 100644 --- a/Firmware/communication/interface_usb.h +++ b/Firmware/communication/interface_usb.h @@ -18,7 +18,7 @@ typedef struct { extern USBStats_t usb_stats_; -void usb_process_packet(uint8_t *buf, uint32_t len); +void usb_process_packet(uint8_t *buf, uint32_t len, uint8_t endpoint_pair); void serve_on_usb(void); #ifdef __cplusplus diff --git a/tools/odrive/usbbulk_transport.py b/tools/odrive/usbbulk_transport.py index eefc33e8..4483bb02 100644 --- a/tools/odrive/usbbulk_transport.py +++ b/tools/odrive/usbbulk_transport.py @@ -46,20 +46,30 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) if platform.system() != 'Windows': self.dev.reset() - interface_number = 1 + #self.dev.set_configuration() # no args: set first configuration + + # Find the best interface + self.cfg = self.dev.get_active_configuration() + custom_interfaces = [i for i in self.cfg.interfaces() if i.bInterfaceClass == 0x00 and i.bInterfaceSubClass == 0x01] + cdc_interfaces = [i for i in self.cfg.interfaces() if i.bInterfaceClass == 0x0a and i.bInterfaceSubClass == 0x00] + all_compatible_interfaces = custom_interfaces + cdc_interfaces + if len(all_compatible_interfaces) == 0: + raise Exception("the device has no compatible interfaces") + self.intf = all_compatible_interfaces[0] + + # Try to detach kernel driver from interface + #interface_number = 1 try: - if self.dev.is_kernel_driver_active(interface_number): - self.dev.detach_kernel_driver(interface_number) + if self.dev.is_kernel_driver_active(self.intf.bInterfaceNumber): + self.dev.detach_kernel_driver(self.intf.bInterfaceNumber) self._printer("Detached Kernel Driver") + else: + self._printer("Kernel Driver was not attached") except NotImplementedError: pass #is_kernel_driver_active not implemented on Windows - self.dev.set_configuration() # no args: set first configuration - self.cfg = self.dev.get_active_configuration() - self.intf = self.cfg[(1,0)] # this implicitly claims the interface - # write endpoint + # find write endpoint (first OUT endpoint) self.epw = usb.util.find_descriptor(self.intf, - # match the first OUT endpoint custom_match = \ lambda e: \ usb.util.endpoint_direction(e.bEndpointAddress) == \ @@ -67,9 +77,8 @@ class USBBulkTransport(odrive.protocol.PacketSource, odrive.protocol.PacketSink) ) assert self.epw is not None self._printer("EndpointAddress for writing {}".format(self.epw.bEndpointAddress)) - # read endpoint + # find read endpoint (first IN endpoint) self.epr = usb.util.find_descriptor(self.intf, - # match the first IN endpoint custom_match = \ lambda e: \ usb.util.endpoint_direction(e.bEndpointAddress) == \ From 3effe7f5bbaa5569df8925359845410c3f094567 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 14 May 2018 15:18:31 -0700 Subject: [PATCH 200/215] fix declaration order --- Firmware/Board/v3/Src/usbd_desc.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Firmware/Board/v3/Src/usbd_desc.c b/Firmware/Board/v3/Src/usbd_desc.c index 45cbf1f6..c07403ac 100644 --- a/Firmware/Board/v3/Src/usbd_desc.c +++ b/Firmware/Board/v3/Src/usbd_desc.c @@ -135,6 +135,8 @@ __ALIGN_BEGIN uint8_t USBD_MS_OS_StringDescriptor[] __ALIGN_END = 0x00 // bPad 1 0x00 Pad field }; +// redefined further down +__ALIGN_BEGIN uint8_t USBD_StrDesc[USBD_MAX_STR_DESC_SIZ] __ALIGN_END; /** * @brief UsrStrDescriptor * return non standard string descriptor From caacde754402cba503ec75144a0d28c2b1ed261b Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 14 May 2018 14:39:25 -0700 Subject: [PATCH 201/215] Replace ASCII protocol compile time setting with runtime setting When enabled, the USB CDC interface of the ODrive will run the ASCII protocol instead of the native protocol. In that case the native protocol can still be used on endpoints 0x03, 0x83. Truly concurrent use is not tested and thus not recommended. --- .travis.yml | 2 +- Firmware/CHANGELOG.md | 3 +++ Firmware/MotorControl/odrive_main.h | 1 + Firmware/Tupfile.lua | 2 -- Firmware/communication/communication.cpp | 1 + Firmware/communication/interface_usb.cpp | 13 +++++++++---- 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8391da91..10c63885 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,7 +46,7 @@ env: # Various protocol combinations - CONFIG_BOARD_VERSION=v3.4-24V CONFIG_USB_PROTOCOL=native-stream CONFIG_UART_PROTOCOL=native - - CONFIG_BOARD_VERSION=v3.4-24V CONFIG_USB_PROTOCOL=stdout CONFIG_UART_PROTOCOL=ascii + - CONFIG_BOARD_VERSION=v3.4-24V CONFIG_USB_PROTOCOL=stdout CONFIG_UART_PROTOCOL=stdout - CONFIG_BOARD_VERSION=v3.4-24V CONFIG_USB_PROTOCOL=none CONFIG_UART_PROTOCOL=none script: diff --git a/Firmware/CHANGELOG.md b/Firmware/CHANGELOG.md index 66a71605..ce21cf1e 100644 --- a/Firmware/CHANGELOG.md +++ b/Firmware/CHANGELOG.md @@ -24,6 +24,9 @@ Please add a note of your changes below this heading if you make a Pull Request. * bake Git-derived firmware version into firmware binary. The firmware version is exposed through the `fw_version_[...]` properties. * Set thread priority of USB pump thread above protocol thread * GPIO3 not sensitive to edges by default +* The device now appears as a composite device on USB. One subdevice is still a CDC device (virtual COM port), the other subdevice is a vendor specific class. This should resolve several issues that were caused by conflicting kernel drivers or OS services. +* Add WinUSB descriptors. This will tell Windows >= 8 to automatically load winusb.sys for the ODrive (only for the vendor specific subdevice). This makes it possible to use the ODrive from userspace via WinUSB with zero configuration. The Python tool currently still uses libusb so Zadig is still required. +* Add a configuration to enable the ASCII protocol on USB at runtime. This will only enable the ASCII protocol on the USB CDC subdevice, not the vendor specific subdevice so the python tools will still be able to talk to the ODrive. ### Fixed * Enums now transported with correct underlying type on native protocol diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index 2b29b6ac..b12a0cd1 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -54,6 +54,7 @@ extern SystemStats_t system_stats_; // @brief general user configurable board configuration struct BoardConfig_t { bool enable_uart = true; + bool enable_ascii_protocol_on_usb = false; float brake_resistance = 0.47f; // [ohm] float dc_bus_undervoltage_trip_level = 8.0f; // #include +#include +#include "ascii_protocol.h" + static uint8_t* usb_buf; static uint32_t usb_len; static uint8_t active_endpoint_pair; @@ -88,13 +91,15 @@ static void usb_server_thread(void * ctx) { if (sem_stat == osOK) { usb_stats_.rx_cnt++; deadline_ms = timeout_to_deadline(PROTOCOL_SERVER_TIMEOUT_MS); + if (active_endpoint_pair == CDC_OUT_EP && board_config.enable_ascii_protocol_on_usb) { + ASCII_protocol_parse_stream(usb_buf, usb_len, usb_stream_output); + } else { #if defined(USB_PROTOCOL_NATIVE) - usb_channel.process_packet(usb_buf, usb_len); + usb_channel.process_packet(usb_buf, usb_len); #elif defined(USB_PROTOCOL_NATIVE_STREAM_BASED) - usb_native_stream_input.process_bytes(usb_buf, usb_len); -#elif defined(USB_PROTOCOL_ASCII) - ASCII_protocol_parse_stream(usb_buf, usb_len, usb_stream_output); + usb_native_stream_input.process_bytes(usb_buf, usb_len); #endif + } USBD_CDC_ReceivePacket(&hUsbDeviceFS, active_endpoint_pair); // Allow next packet } } From 62a599bf6ed49f8a27650e61f1a5b653cf9bcc98 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 14 May 2018 16:13:51 -0700 Subject: [PATCH 202/215] fix ascii protocol compilation --- Firmware/communication/interface_usb.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Firmware/communication/interface_usb.cpp b/Firmware/communication/interface_usb.cpp index 28891a82..b044cdd6 100644 --- a/Firmware/communication/interface_usb.cpp +++ b/Firmware/communication/interface_usb.cpp @@ -2,6 +2,8 @@ #include "interface_usb.h" #include "protocol.hpp" +#include "ascii_protocol.h" + #include #include From 0e32df329fd420793bf8317052de8ccaa41bee90 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 14 May 2018 16:25:31 -0700 Subject: [PATCH 203/215] fix compilation when using stdout protocol --- Firmware/communication/communication.cpp | 4 ++-- Firmware/communication/interface_uart.cpp | 1 + Firmware/communication/interface_uart.h | 3 +++ Firmware/communication/interface_usb.cpp | 1 + Firmware/communication/interface_usb.h | 3 +++ 5 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 1c4accfd..d1a8e487 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -182,10 +182,10 @@ int _write(int file, const char* data, int len); // @brief This is what printf calls internally int _write(int file, const char* data, int len) { #ifdef USB_PROTOCOL_STDOUT - usb_stream_output.process_bytes((const uint8_t *)data, len); + usb_stream_output_ptr->process_bytes((const uint8_t *)data, len); #endif #ifdef UART_PROTOCOL_STDOUT - uart4_stream_output.process_bytes((const uint8_t *)data, len); + uart4_stream_output_ptr->process_bytes((const uint8_t *)data, len); #endif return len; } diff --git a/Firmware/communication/interface_uart.cpp b/Firmware/communication/interface_uart.cpp index 90d976aa..58b4f57d 100644 --- a/Firmware/communication/interface_uart.cpp +++ b/Firmware/communication/interface_uart.cpp @@ -48,6 +48,7 @@ public: private: uint8_t tx_buf_[UART_TX_BUFFER_SIZE]; } uart4_stream_output; +StreamSink* uart4_stream_output_ptr = &uart4_stream_output; PacketToStreamConverter uart4_packet_output(uart4_stream_output); BidirectionalPacketBasedChannel uart4_channel(uart4_packet_output); diff --git a/Firmware/communication/interface_uart.h b/Firmware/communication/interface_uart.h index b5f1ed72..8ad71c19 100644 --- a/Firmware/communication/interface_uart.h +++ b/Firmware/communication/interface_uart.h @@ -2,6 +2,9 @@ #define __INTERFACE_UART_HPP #ifdef __cplusplus +#include "protocol.hpp" +extern StreamSink* uart4_stream_output_ptr; + extern "C" { #endif diff --git a/Firmware/communication/interface_usb.cpp b/Firmware/communication/interface_usb.cpp index b044cdd6..5687e186 100644 --- a/Firmware/communication/interface_usb.cpp +++ b/Firmware/communication/interface_usb.cpp @@ -67,6 +67,7 @@ public: private: PacketSink& output_; } usb_stream_output(usb_packet_output); +StreamSink* usb_stream_output_ptr = &usb_stream_output; #if defined(USB_PROTOCOL_NATIVE) BidirectionalPacketBasedChannel usb_channel(usb_packet_output); diff --git a/Firmware/communication/interface_usb.h b/Firmware/communication/interface_usb.h index a56bca36..9038d822 100644 --- a/Firmware/communication/interface_usb.h +++ b/Firmware/communication/interface_usb.h @@ -2,6 +2,9 @@ #define __INTERFACE_USB_HPP #ifdef __cplusplus +#include "protocol.hpp" +extern StreamSink* usb_stream_output_ptr; + extern "C" { #endif From e0b0824783e996410c9172430d8a6f0727525a13 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 14 May 2018 16:29:53 -0700 Subject: [PATCH 204/215] add CI jobs for board version 3.5 --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 8391da91..5dd08769 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,6 +43,8 @@ env: - CONFIG_BOARD_VERSION=v3.3 DEPLOY=v3.3 - CONFIG_BOARD_VERSION=v3.4-24V DEPLOY=v3.4-24V - CONFIG_BOARD_VERSION=v3.4-48V DEPLOY=v3.4-48V + - CONFIG_BOARD_VERSION=v3.5-24V DEPLOY=v3.5-24V + - CONFIG_BOARD_VERSION=v3.5-48V DEPLOY=v3.5-48V # Various protocol combinations - CONFIG_BOARD_VERSION=v3.4-24V CONFIG_USB_PROTOCOL=native-stream CONFIG_UART_PROTOCOL=native From 08fd4082411516c49fd724acf232711c306b6184 Mon Sep 17 00:00:00 2001 From: Paul Guenette Date: Thu, 26 Apr 2018 00:04:21 -0400 Subject: [PATCH 205/215] Add GPIO translation functions --- Firmware/Board/v3/Inc/gpio.h | 2 ++ Firmware/Board/v3/Src/gpio.c | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/Firmware/Board/v3/Inc/gpio.h b/Firmware/Board/v3/Inc/gpio.h index 6ec71035..da0311af 100644 --- a/Firmware/Board/v3/Inc/gpio.h +++ b/Firmware/Board/v3/Inc/gpio.h @@ -77,6 +77,8 @@ bool GPIO_subscribe(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin, void GPIO_unsubscribe(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin); void GPIO_set_to_analog(GPIO_TypeDef* GPIO_port, uint16_t GPIO_pin); +uint16_t get_gpio_pin_by_pin(uint16_t GPIO_pin); +GPIO_TypeDef* get_gpio_port_by_pin(uint16_t GPIO_pin); /* USER CODE END Prototypes */ diff --git a/Firmware/Board/v3/Src/gpio.c b/Firmware/Board/v3/Src/gpio.c index 130708bf..1114da30 100644 --- a/Firmware/Board/v3/Src/gpio.c +++ b/Firmware/Board/v3/Src/gpio.c @@ -277,6 +277,26 @@ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_pin) { } } +GPIO_TypeDef* get_gpio_port_by_pin(uint16_t GPIO_pin){ + switch(GPIO_pin){ + case 1: return GPIO_1_GPIO_Port; break; + case 2: return GPIO_2_GPIO_Port; break; + case 3: return GPIO_3_GPIO_Port; break; + case 4: return GPIO_4_GPIO_Port; break; + case 5: return GPIO_5_GPIO_Port; break; + } +} + +uint16_t get_gpio_pin_by_pin(uint16_t GPIO_pin){ + switch(GPIO_pin){ + case 1: return GPIO_1_Pin; break; + case 2: return GPIO_2_Pin; break; + case 3: return GPIO_3_Pin; break; + case 4: return GPIO_4_Pin; break; + case 5: return GPIO_5_Pin; break; + } +} + /* USER CODE END 2 */ /** From 8339311c3521bbfce050045821fdb0c4d9d123af Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Mon, 14 May 2018 18:59:23 -0700 Subject: [PATCH 206/215] make ADC work --- Firmware/Board/v3/Src/gpio.c | 2 ++ .../Board/v3/Src/prev_board_ver/adc_V3_2.c | 20 +++++++++++++++++++ .../Board/v3/Src/prev_board_ver/adc_V3_4.c | 20 +++++++++++++++++++ Firmware/MotorControl/axis.hpp | 2 +- Firmware/MotorControl/main.cpp | 17 ++++++++++++++++ Firmware/communication/communication.cpp | 6 +++--- 6 files changed, 63 insertions(+), 4 deletions(-) diff --git a/Firmware/Board/v3/Src/gpio.c b/Firmware/Board/v3/Src/gpio.c index 1114da30..4e0c1d41 100644 --- a/Firmware/Board/v3/Src/gpio.c +++ b/Firmware/Board/v3/Src/gpio.c @@ -284,6 +284,7 @@ GPIO_TypeDef* get_gpio_port_by_pin(uint16_t GPIO_pin){ case 3: return GPIO_3_GPIO_Port; break; case 4: return GPIO_4_GPIO_Port; break; case 5: return GPIO_5_GPIO_Port; break; + default: return GPIO_1_GPIO_Port; } } @@ -294,6 +295,7 @@ uint16_t get_gpio_pin_by_pin(uint16_t GPIO_pin){ case 3: return GPIO_3_Pin; break; case 4: return GPIO_4_Pin; break; case 5: return GPIO_5_Pin; break; + default: return GPIO_1_Pin; } } diff --git a/Firmware/Board/v3/Src/prev_board_ver/adc_V3_2.c b/Firmware/Board/v3/Src/prev_board_ver/adc_V3_2.c index 2496df7f..bc2ddddf 100644 --- a/Firmware/Board/v3/Src/prev_board_ver/adc_V3_2.c +++ b/Firmware/Board/v3/Src/prev_board_ver/adc_V3_2.c @@ -2,6 +2,7 @@ ADC_HandleTypeDef hadc1; ADC_HandleTypeDef hadc2; ADC_HandleTypeDef hadc3; +DMA_HandleTypeDef hdma_adc1; /* ADC1 init function */ void MX_ADC1_Init(void) @@ -195,6 +196,25 @@ void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle) GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + /* ADC1 DMA Init */ + /* ADC1 Init */ + hdma_adc1.Instance = DMA2_Stream0; + hdma_adc1.Init.Channel = DMA_CHANNEL_0; + hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY; + hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE; + hdma_adc1.Init.MemInc = DMA_MINC_ENABLE; + hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; + hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + hdma_adc1.Init.Mode = DMA_CIRCULAR; + hdma_adc1.Init.Priority = DMA_PRIORITY_LOW; + hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + if (HAL_DMA_Init(&hdma_adc1) != HAL_OK) + { + _Error_Handler(__FILE__, __LINE__); + } + + __HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1); + /* ADC1 interrupt Init */ HAL_NVIC_SetPriority(ADC_IRQn, 5, 0); HAL_NVIC_EnableIRQ(ADC_IRQn); diff --git a/Firmware/Board/v3/Src/prev_board_ver/adc_V3_4.c b/Firmware/Board/v3/Src/prev_board_ver/adc_V3_4.c index 49862c97..31ce77d0 100644 --- a/Firmware/Board/v3/Src/prev_board_ver/adc_V3_4.c +++ b/Firmware/Board/v3/Src/prev_board_ver/adc_V3_4.c @@ -2,6 +2,7 @@ ADC_HandleTypeDef hadc1; ADC_HandleTypeDef hadc2; ADC_HandleTypeDef hadc3; +DMA_HandleTypeDef hdma_adc1; /* ADC1 init function */ void MX_ADC1_Init(void) @@ -194,6 +195,25 @@ void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle) GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); + /* ADC1 DMA Init */ + /* ADC1 Init */ + hdma_adc1.Instance = DMA2_Stream0; + hdma_adc1.Init.Channel = DMA_CHANNEL_0; + hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY; + hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE; + hdma_adc1.Init.MemInc = DMA_MINC_ENABLE; + hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; + hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; + hdma_adc1.Init.Mode = DMA_CIRCULAR; + hdma_adc1.Init.Priority = DMA_PRIORITY_LOW; + hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + if (HAL_DMA_Init(&hdma_adc1) != HAL_OK) + { + _Error_Handler(__FILE__, __LINE__); + } + + __HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1); + /* ADC1 interrupt Init */ HAL_NVIC_SetPriority(ADC_IRQn, 5, 0); HAL_NVIC_EnableIRQ(ADC_IRQn); diff --git a/Firmware/MotorControl/axis.hpp b/Firmware/MotorControl/axis.hpp index 8e2245d4..9ad4804a 100644 --- a/Firmware/MotorControl/axis.hpp +++ b/Firmware/MotorControl/axis.hpp @@ -26,7 +26,7 @@ struct AxisConfig_t { bool startup_encoder_offset_calibration = false; //= 5 + GPIO_InitStruct.Pin = GPIO_5_Pin; + HAL_GPIO_Init(GPIO_5_GPIO_Port, &GPIO_InitStruct); +#endif + // Construct all objects. for (size_t i = 0; i < AXIS_COUNT; ++i) { Encoder *encoder = new Encoder(hw_configs[i].encoder_config, diff --git a/Firmware/communication/communication.cpp b/Firmware/communication/communication.cpp index 82bcee14..66f498df 100644 --- a/Firmware/communication/communication.cpp +++ b/Firmware/communication/communication.cpp @@ -18,7 +18,7 @@ //#include //#include //#include -//#include +#include #include @@ -92,7 +92,7 @@ public: void NVIC_SystemReset_helper() { NVIC_SystemReset(); } void enter_dfu_mode_helper() { enter_dfu_mode(); } float get_oscilloscope_val(uint32_t index) { return oscilloscope[index]; } - //float get_adc_voltage_(uint32_t gpio) { return get_adc_voltage(gpio_port(gpio), gpio_pin(gpio)); } + float get_adc_voltage_(uint32_t gpio) { return get_adc_voltage(get_gpio_port_by_pin(gpio), get_gpio_pin_by_pin(gpio)); } int32_t test_function(int32_t delta) { static int cnt = 0; return cnt += delta; } } static_functions; @@ -140,7 +140,7 @@ static inline auto make_obj_tree() { make_protocol_property("test_property", &test_property), make_protocol_function("test_function", static_functions, &StaticFunctions::test_function, "delta"), make_protocol_function("get_oscilloscope_val", static_functions, &StaticFunctions::get_oscilloscope_val, "index"), - //make_protocol_function("get_adc_voltage", static_functions, &StaticFunctions::get_adc_voltage_, "channel"), + make_protocol_function("get_adc_voltage", static_functions, &StaticFunctions::get_adc_voltage_, "gpio"), make_protocol_function("save_configuration", static_functions, &StaticFunctions::save_configuration_helper), make_protocol_function("erase_configuration", static_functions, &StaticFunctions::erase_configuration_helper), make_protocol_function("reboot", static_functions, &StaticFunctions::NVIC_SystemReset_helper), From a8267325ed05aa098730a763718d3a4521759217 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Tue, 15 May 2018 21:37:49 -0700 Subject: [PATCH 207/215] fix ascii protocol and add automated test for it --- Firmware/communication/ascii_protocol.cpp | 1 - Firmware/communication/interface_uart.cpp | 2 +- tools/odrive/tests.py | 91 +++++++++++++++++++++-- tools/run_tests.py | 2 + 4 files changed, 88 insertions(+), 8 deletions(-) diff --git a/Firmware/communication/ascii_protocol.cpp b/Firmware/communication/ascii_protocol.cpp index 98f1cd02..78211f41 100644 --- a/Firmware/communication/ascii_protocol.cpp +++ b/Firmware/communication/ascii_protocol.cpp @@ -124,7 +124,6 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink& respond(response_channel, use_checksum, "invalid motor %u", motor_number); } else { axes[motor_number]->controller_.set_current_setpoint(current_setpoint); - respond(response_channel, use_checksum, "ok", motor_number); } } else if (cmd[0] == 'i'){ // Dump device info diff --git a/Firmware/communication/interface_uart.cpp b/Firmware/communication/interface_uart.cpp index 58b4f57d..7d17e528 100644 --- a/Firmware/communication/interface_uart.cpp +++ b/Firmware/communication/interface_uart.cpp @@ -95,7 +95,7 @@ void serve_on_uart() { dma_last_rcv_idx = UART_RX_BUFFER_SIZE - huart4.hdmarx->Instance->NDTR; // Start UART communication thread - osThreadDef(uart_server_thread_def, uart_server_thread, osPriorityNormal, 0, 512); + osThreadDef(uart_server_thread_def, uart_server_thread, osPriorityNormal, 0, 1024 /* the ascii protocol needs considerable stack space */); uart_thread = osThreadCreate(osThread(uart_server_thread_def), NULL); } diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index 43ef182b..f5dc850a 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -51,12 +51,22 @@ class AxisTestContext(): self.odrv_ctx = odrv_ctx def test_assert_eq(observed, expected, range=None, accuracy=None): - if range is None and accuracy is None and observed != expected: - raise TestFailed("value mismatch: expected {} but observed {}".format(expected, observed)) - if not range is None and ((observed < expected - range) or (observed > expected + range)): - raise TestFailed("value out of range: expected {}+-{} but observed {}".format(expected, range, observed)) - elif not accuracy is None and ((observed < expected * (1 - accuracy)) or (observed > expected * (1 + accuracy))): - raise TestFailed("value out of range: expected {}+-{}% but observed {}".format(expected, accuracy*100.0, observed)) + sign = lambda x: 1 if x >= 0 else -1 + + # Comparision with absolute range + if not range is None: + if (observed < expected - range) or (observed > expected + range): + raise TestFailed("value out of range: expected {}+-{} but observed {}".format(expected, range, observed)) + + # Comparision with relative range + elif not accuracy is None: + if sign(observed) != sign(expected) or (abs(observed) < abs(expected) * (1 - accuracy)) or (abs(observed) > abs(expected) * (1 + accuracy)): + raise TestFailed("value out of range: expected {}+-{}% but observed {}".format(expected, accuracy*100.0, observed)) + + # Exact comparision + else: + if observed != expected: + raise TestFailed("value mismatch: expected {} but observed {}".format(expected, observed)) def get_errors(axis_ctx: AxisTestContext): errors = [] @@ -669,3 +679,72 @@ class TestVelCtrlVsPosCtrl(DualAxisTest): #init_pos = axis1_ctx.handle.encoder.pos_estimate #axis1_ctx.handle.controller.set_pos_setpoint(init_pos + 100000, 0, 0) #request_state(axis1_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL) + + +# ASCII protocol helper functions +def gcode_calc_checksum(data): + from functools import reduce + return reduce(lambda a, b: a ^ b, data) +def gcode_append_checksum(data): + return data + b'*' + str(gcode_calc_checksum(data)).encode('ascii') +def get_lines(port): + buf = port.get_bytes(512, time.monotonic() + 0.2) + return [line.rstrip(b'\r') for line in buf.split(b'\n') if line.rstrip(b'\r')] + +class TestAsciiProtocol(ODriveTest): + def run_test(self, odrv_ctx: ODriveTestContext, logger): + import odrive.serial_transport + port = odrive.serial_transport.SerialStreamTransport(odrv_ctx.yaml['uart'], 115200) + + # send garbage to throw the device off track + port.process_bytes(b"garbage\r\n\r\0trash\n") + port.process_bytes(b"\n") # start a new clean line + get_lines(port) # flush RX buffer + + # info command without checksum + port.process_bytes(b"i\n") + # check if it reports the serial number (among other things) + lines = get_lines(port) + expected_line = ('Serial number: ' + odrv_ctx.yaml['serial-number']).encode('ascii') + if not expected_line in lines: + raise Exception("expected {} in ASCII protocol response but got {}".format(expected_line, str(lines))) + + # info command with checksum + port.process_bytes(gcode_append_checksum(b"i") + b" ; a useless comment\n") + # check if it reports the serial number with checksum (among other things) + lines = get_lines(port) + expected_line = gcode_append_checksum(('Serial number: ' + odrv_ctx.yaml['serial-number']).encode('ascii')) + if not expected_line in lines: + raise Exception("expected {} in ASCII protocol response but got {}".format(expected_line, str(lines))) + + port.process_bytes(b"p 0 2000 -10 0.002\n") + time.sleep(0.01) # 1ms is too short, 2ms usually works, 10ms for good measure + test_assert_eq(odrv_ctx.handle.axis0.controller.pos_setpoint, 2000, accuracy=0.001) + test_assert_eq(odrv_ctx.handle.axis0.controller.vel_setpoint, -10, accuracy=0.001) + test_assert_eq(odrv_ctx.handle.axis0.controller.current_setpoint, 0.002, accuracy=0.001) + + port.process_bytes(b"v 1 -21.1 0.32\n") + time.sleep(0.01) + test_assert_eq(odrv_ctx.handle.axis1.controller.vel_setpoint, -21.1, accuracy=0.001) + test_assert_eq(odrv_ctx.handle.axis1.controller.current_setpoint, 0.32, accuracy=0.001) + + port.process_bytes(b"c 0 0.1\n") + time.sleep(0.01) + test_assert_eq(odrv_ctx.handle.axis0.controller.current_setpoint, 0.1, accuracy=0.001) + + # write arbitrary parameter + port.process_bytes(b"w axis0.controller.pos_setpoint -123.456 ; comment\n") + time.sleep(0.01) + test_assert_eq(odrv_ctx.handle.axis0.controller.pos_setpoint, -123.456, accuracy=0.001) + + port.process_bytes(b"r axis0.controller.pos_setpoint\n") + lines = get_lines(port) + expected_line = b'-123.4560' + if lines != [expected_line]: + raise Exception("expected {} in ASCII protocol response but got {}".format(expected_line, str(lines))) + + # disable axes + odrv_ctx.handle.axis0.controller.set_pos_setpoint(0, 0, 0) + odrv_ctx.handle.axis1.controller.set_pos_setpoint(0, 0, 0) + request_state(odrv_ctx.axes[0], AXIS_STATE_IDLE) + request_state(odrv_ctx.axes[1], AXIS_STATE_IDLE) diff --git a/tools/run_tests.py b/tools/run_tests.py index 1cb1bcd8..478dd9af 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -87,6 +87,8 @@ else: all_tests.append(TestDiscoverAndGotoIdle()) all_tests.append(TestEncoderOffsetCalibration(pass_if_ready=True)) +all_tests.append(TestAsciiProtocol()) + if test_rig_yaml['type'] == 'parallel': #all_tests.append(TestHighVelocity()) all_tests.append(TestHighVelocityInViscousFluid(load_current=35, driver_current=45)) From 8ffa8333c552e04c0c2d30c7a07d6eaa52139855 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Wed, 16 May 2018 00:33:02 -0700 Subject: [PATCH 208/215] [UNTESTED] start implementing sensorless test --- Firmware/MotorControl/controller.hpp | 2 +- tools/odrive/tests.py | 17 +++++++++++++++++ tools/run_tests.py | 4 ++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Firmware/MotorControl/controller.hpp b/Firmware/MotorControl/controller.hpp index c76161ea..f10b6211 100644 --- a/Firmware/MotorControl/controller.hpp +++ b/Firmware/MotorControl/controller.hpp @@ -18,7 +18,7 @@ struct ControllerConfig_t { Motor_control_mode_t control_mode = CTRL_MODE_POSITION_CONTROL; //see: Motor_control_mode_t float pos_gain = 20.0f; // [(counts/s) / counts] float vel_gain = 5.0f / 10000.0f; // [A/(counts/s)] - // float vel_gain = 15.0f / 200.0f, // [A/(rad/s)] + // float vel_gain = 5.0f / 200.0f, // [A/(rad/s)] float vel_integrator_gain = 10.0f / 10000.0f; // [A/(counts/s * s)] float vel_limit = 20000.0f; // [counts/s] }; diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index f5dc850a..9f320601 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -164,6 +164,9 @@ def get_max_rpm(axis_ctx: AxisTestContext): rated_rpm = min(base_speed_rpm, axis_ctx.yaml['encoder-max-rpm']) return rated_rpm +def get_sensorless_vel(axis_ctx: AxisTestContext, vel): + return vel * 2 * math.pi / axis_ctx.yaml['encoder-cpr'] * axis_ctx.yaml['motor-pole-pairs'] + class ODriveTest(ABC): """ Tests inheriting from this class get full ownership of the ODrive @@ -748,3 +751,17 @@ class TestAsciiProtocol(ODriveTest): odrv_ctx.handle.axis1.controller.set_pos_setpoint(0, 0, 0) request_state(odrv_ctx.axes[0], AXIS_STATE_IDLE) request_state(odrv_ctx.axes[1], AXIS_STATE_IDLE) + + +class TestSensorlessControl(AxisTest): + def run_test(self, axis_ctx: AxisTestContext, logger): + odrv0.axis0.controller.config.vel_gain = 5 / get_sensorless_vel(axis_ctx, 10000) + odrv0.axis0.controller.config.vel_integrator_gain = 10 / get_sensorless_vel(axis_ctx, 10000) + target_vel = get_sensorless_vel(axis_ctx, 20000) + axis_ctx.handle.controller.set_vel_setpoint(target_vel, 0) + request_state(axis_ctx, AXIS_STATE_SENSORLESS_CONTROL) + # wait for spinup + time.sleep(2) + test_assert_eq(odrv0.axis0.encoder.pll_vel, target_vel, range=2000) + + request_state(axis_ctx, AXIS_STATE_IDLE) diff --git a/tools/run_tests.py b/tools/run_tests.py index 478dd9af..2293c573 100755 --- a/tools/run_tests.py +++ b/tools/run_tests.py @@ -88,6 +88,10 @@ else: all_tests.append(TestEncoderOffsetCalibration(pass_if_ready=True)) all_tests.append(TestAsciiProtocol()) +all_tests.append(TestSensorlessControl()) + +#all_tests.append(TestStepDirInput()) +#all_tests.append(TestPWMInput()) if test_rig_yaml['type'] == 'parallel': #all_tests.append(TestHighVelocity()) From 7bc9acada527aea63e3d752c1376bbd8c77e17b1 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 1 Jun 2018 13:22:49 -0700 Subject: [PATCH 209/215] maybe make enums work with ascii --- Firmware/communication/protocol.hpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Firmware/communication/protocol.hpp b/Firmware/communication/protocol.hpp index f51fc2de..2d975210 100644 --- a/Firmware/communication/protocol.hpp +++ b/Firmware/communication/protocol.hpp @@ -551,8 +551,8 @@ ProtocolObject make_protocol_object(const char * name, TMembers&&.. // TODO: move to cpp_utils -#define ENABLE_IF_SAME(a, b, type) \ - template typename std::enable_if_t::value, bool> +#define ENABLE_IF_SAME_OR_ENUM(a, b, type) \ + template typename std::enable_if_t::value || std::is_enum::value, type> template class ProtocolProperty : public Endpoint { @@ -620,22 +620,22 @@ public: // *** ASCII protocol handlers *** - ENABLE_IF_SAME(std::decay_t, float, bool) + ENABLE_IF_SAME_OR_ENUM(std::decay_t, float, bool) get_string_ex(char * buffer, size_t length, int) { snprintf(buffer, length, "%f", *property_); return true; } - ENABLE_IF_SAME(std::decay_t, int32_t, bool) + ENABLE_IF_SAME_OR_ENUM(std::decay_t, int32_t, bool) get_string_ex(char * buffer, size_t length, int) { snprintf(buffer, length, "%ld", *property_); return true; } - ENABLE_IF_SAME(std::decay_t, uint32_t, bool) + ENABLE_IF_SAME_OR_ENUM(std::decay_t, uint32_t, bool) get_string_ex(char * buffer, size_t length, int) { snprintf(buffer, length, "%lu", *property_); return true; } - ENABLE_IF_SAME(std::decay_t, bool, bool) + ENABLE_IF_SAME_OR_ENUM(std::decay_t, bool, bool) get_string_ex(char * buffer, size_t length, int) { buffer[0] = (*property_) ? '1' : '0'; buffer[1] = 0; @@ -647,19 +647,20 @@ public: bool get_string(char * buffer, size_t length) final { return get_string_ex(buffer, length, 0); } - ENABLE_IF_SAME(TProperty, float, bool) + + ENABLE_IF_SAME_OR_ENUM(TProperty, float, bool) set_string_ex(char * buffer, size_t length, int) { return sscanf(buffer, "%f", property_) == 1; } - ENABLE_IF_SAME(TProperty, int32_t, bool) + ENABLE_IF_SAME_OR_ENUM(TProperty, int32_t, bool) set_string_ex(char * buffer, size_t length, int) { return sscanf(buffer, "%ld", property_) == 1; } - ENABLE_IF_SAME(TProperty, uint32_t, bool) + ENABLE_IF_SAME_OR_ENUM(TProperty, uint32_t, bool) set_string_ex(char * buffer, size_t length, int) { return sscanf(buffer, "%lu", property_) == 1; } - ENABLE_IF_SAME(TProperty, bool, bool) + ENABLE_IF_SAME_OR_ENUM(TProperty, bool, bool) set_string_ex(char * buffer, size_t length, int) { int val; if (sscanf(buffer, "%d", &val) != 1) From 53449f0fda31bcf405cf36c64441fcaa614c048b Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 1 Jun 2018 13:38:13 -0700 Subject: [PATCH 210/215] add axis state change test --- tools/odrive/tests.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tools/odrive/tests.py b/tools/odrive/tests.py index 1a1e22ca..0fb64cbb 100644 --- a/tools/odrive/tests.py +++ b/tools/odrive/tests.py @@ -746,6 +746,18 @@ class TestAsciiProtocol(ODriveTest): if lines != [expected_line]: raise Exception("expected {} in ASCII protocol response but got {}".format(expected_line, str(lines))) + # read/write enums + port.process_bytes(b"r axis0.error\n") + lines = get_lines(port) + expected_line = b'0' + if lines != [expected_line]: + raise Exception("expected {} in ASCII protocol response but got {}".format(expected_line, str(lines))) + + test_assert_eq(odrv_ctx.axes[0].handle.current_state, AXIS_STATE_CLOSED_LOOP_CONTROL) + port.process_bytes(b"w axis0.requested_state {}\n".format(AXIS_STATE_IDLE)) + time.sleep(0.01) + test_assert_eq(odrv_ctx.axes[0].handle.current_state, AXIS_STATE_IDLE) + # disable axes odrv_ctx.handle.axis0.controller.set_pos_setpoint(0, 0, 0) odrv_ctx.handle.axis1.controller.set_pos_setpoint(0, 0, 0) From 5812c06d58f7bb22994830821add100a511d7a47 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 1 Jun 2018 15:32:24 -0700 Subject: [PATCH 211/215] change back the enum attempts --- Firmware/communication/protocol.hpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Firmware/communication/protocol.hpp b/Firmware/communication/protocol.hpp index 2d975210..baa2ddea 100644 --- a/Firmware/communication/protocol.hpp +++ b/Firmware/communication/protocol.hpp @@ -551,8 +551,8 @@ ProtocolObject make_protocol_object(const char * name, TMembers&&.. // TODO: move to cpp_utils -#define ENABLE_IF_SAME_OR_ENUM(a, b, type) \ - template typename std::enable_if_t::value || std::is_enum::value, type> +#define ENABLE_IF_SAME(a, b, type) \ + template typename std::enable_if_t::value, type> template class ProtocolProperty : public Endpoint { @@ -620,22 +620,22 @@ public: // *** ASCII protocol handlers *** - ENABLE_IF_SAME_OR_ENUM(std::decay_t, float, bool) + ENABLE_IF_SAME(std::decay_t, float, bool) get_string_ex(char * buffer, size_t length, int) { snprintf(buffer, length, "%f", *property_); return true; } - ENABLE_IF_SAME_OR_ENUM(std::decay_t, int32_t, bool) + ENABLE_IF_SAME(std::decay_t, int32_t, bool) get_string_ex(char * buffer, size_t length, int) { snprintf(buffer, length, "%ld", *property_); return true; } - ENABLE_IF_SAME_OR_ENUM(std::decay_t, uint32_t, bool) + ENABLE_IF_SAME(std::decay_t, uint32_t, bool) get_string_ex(char * buffer, size_t length, int) { snprintf(buffer, length, "%lu", *property_); return true; } - ENABLE_IF_SAME_OR_ENUM(std::decay_t, bool, bool) + ENABLE_IF_SAME(std::decay_t, bool, bool) get_string_ex(char * buffer, size_t length, int) { buffer[0] = (*property_) ? '1' : '0'; buffer[1] = 0; @@ -648,19 +648,19 @@ public: return get_string_ex(buffer, length, 0); } - ENABLE_IF_SAME_OR_ENUM(TProperty, float, bool) + ENABLE_IF_SAME(TProperty, float, bool) set_string_ex(char * buffer, size_t length, int) { return sscanf(buffer, "%f", property_) == 1; } - ENABLE_IF_SAME_OR_ENUM(TProperty, int32_t, bool) + ENABLE_IF_SAME(TProperty, int32_t, bool) set_string_ex(char * buffer, size_t length, int) { return sscanf(buffer, "%ld", property_) == 1; } - ENABLE_IF_SAME_OR_ENUM(TProperty, uint32_t, bool) + ENABLE_IF_SAME(TProperty, uint32_t, bool) set_string_ex(char * buffer, size_t length, int) { return sscanf(buffer, "%lu", property_) == 1; } - ENABLE_IF_SAME_OR_ENUM(TProperty, bool, bool) + ENABLE_IF_SAME(TProperty, bool, bool) set_string_ex(char * buffer, size_t length, int) { int val; if (sscanf(buffer, "%d", &val) != 1) From 676ccda3cbecac2bb89390952b8d5961738c8b66 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Fri, 1 Jun 2018 15:37:24 -0700 Subject: [PATCH 212/215] add smaller int type specilizatons --- Firmware/communication/protocol.hpp | 36 +++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Firmware/communication/protocol.hpp b/Firmware/communication/protocol.hpp index baa2ddea..a1082575 100644 --- a/Firmware/communication/protocol.hpp +++ b/Firmware/communication/protocol.hpp @@ -635,6 +635,26 @@ public: snprintf(buffer, length, "%lu", *property_); return true; } + ENABLE_IF_SAME(std::decay_t, int16_t, bool) + get_string_ex(char * buffer, size_t length, int) { + snprintf(buffer, length, "%hd", *property_); + return true; + } + ENABLE_IF_SAME(std::decay_t, uint16_t, bool) + get_string_ex(char * buffer, size_t length, int) { + snprintf(buffer, length, "%hu", *property_); + return true; + } + ENABLE_IF_SAME(std::decay_t, int8_t, bool) + get_string_ex(char * buffer, size_t length, int) { + snprintf(buffer, length, "%hhd", *property_); + return true; + } + ENABLE_IF_SAME(std::decay_t, uint8_t, bool) + get_string_ex(char * buffer, size_t length, int) { + snprintf(buffer, length, "%hhu", *property_); + return true; + } ENABLE_IF_SAME(std::decay_t, bool, bool) get_string_ex(char * buffer, size_t length, int) { buffer[0] = (*property_) ? '1' : '0'; @@ -660,6 +680,22 @@ public: set_string_ex(char * buffer, size_t length, int) { return sscanf(buffer, "%lu", property_) == 1; } + ENABLE_IF_SAME(TProperty, int16_t, bool) + set_string_ex(char * buffer, size_t length, int) { + return sscanf(buffer, "%hd", property_) == 1; + } + ENABLE_IF_SAME(TProperty, uint16_t, bool) + set_string_ex(char * buffer, size_t length, int) { + return sscanf(buffer, "%hu", property_) == 1; + } + ENABLE_IF_SAME(TProperty, int8_t, bool) + set_string_ex(char * buffer, size_t length, int) { + return sscanf(buffer, "%hhd", property_) == 1; + } + ENABLE_IF_SAME(TProperty, uint8_t, bool) + set_string_ex(char * buffer, size_t length, int) { + return sscanf(buffer, "%hhu", property_) == 1; + } ENABLE_IF_SAME(TProperty, bool, bool) set_string_ex(char * buffer, size_t length, int) { int val; From c95233a57b1a28de409ce8c8f7eb4befe3bf994d Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Tue, 5 Jun 2018 21:59:48 -0700 Subject: [PATCH 213/215] make ascii protocol default on CDC interface, change some naming --- Firmware/Board/v3/Src/usbd_desc.c | 5 +++-- Firmware/MotorControl/odrive_main.h | 2 +- Firmware/communication/communication.cpp | 4 ++-- Firmware/communication/interface_uart.cpp | 2 +- Firmware/communication/interface_uart.h | 2 +- Firmware/communication/interface_usb.cpp | 2 +- Firmware/communication/interface_usb.h | 2 +- tools/odrive/enums.py | 6 +++--- 8 files changed, 13 insertions(+), 12 deletions(-) diff --git a/Firmware/Board/v3/Src/usbd_desc.c b/Firmware/Board/v3/Src/usbd_desc.c index c07403ac..d213f64a 100644 --- a/Firmware/Board/v3/Src/usbd_desc.c +++ b/Firmware/Board/v3/Src/usbd_desc.c @@ -97,7 +97,8 @@ #define USBD_PID_FS 0x0D32 #define USBD_PRODUCT_XSTR(s) USBD_PRODUCT_STR(s) #define USBD_PRODUCT_STR(s) #s -#define USBD_PRODUCT_STRING_FS ODrive version HW_VERSION_MAJOR.HW_VERSION_MINOR +#define USBD_PRODUCT_STRING_FS ODrive HW_VERSION_MAJOR.HW_VERSION_MINOR CDC Interface +#define NATIVE_STRING ODrive HW_VERSION_MAJOR.HW_VERSION_MINOR Native Interface #define USBD_SERIALNUMBER_STRING_FS "000000000001" #define USBD_CONFIGURATION_STRING_FS "CDC Config" #define USBD_INTERFACE_STRING_FS "CDC Interface" @@ -152,7 +153,7 @@ uint8_t * USBD_UsrStrDescriptor(struct _USBD_HandleTypeDef *pdev, uint8_t index, *length = sizeof (USBD_MS_OS_StringDescriptor); return USBD_MS_OS_StringDescriptor; } else if (USBD_IDX_ODRIVE_INTF_STR == index) { - USBD_GetString((uint8_t *)"ODrive Interface", USBD_StrDesc, length); + USBD_GetString((uint8_t *)USBD_PRODUCT_XSTR(NATIVE_STRING), USBD_StrDesc, length); return USBD_StrDesc; } return NULL; diff --git a/Firmware/MotorControl/odrive_main.h b/Firmware/MotorControl/odrive_main.h index b12a0cd1..aff37201 100644 --- a/Firmware/MotorControl/odrive_main.h +++ b/Firmware/MotorControl/odrive_main.h @@ -54,7 +54,7 @@ extern SystemStats_t system_stats_; // @brief general user configurable board configuration struct BoardConfig_t { bool enable_uart = true; - bool enable_ascii_protocol_on_usb = false; + bool enable_ascii_protocol_on_usb = true; float brake_resistance = 0.47f; // [ohm] float dc_bus_undervoltage_trip_level = 8.0f; //(*tree_ptr); set_application_endpoints(&endpoint_provider); - serve_on_uart(); - serve_on_usb(); + start_uart_server(); + start_usb_server(); for (;;) { osDelay(1000); // nothing to do diff --git a/Firmware/communication/interface_uart.cpp b/Firmware/communication/interface_uart.cpp index 58b4f57d..d5a47190 100644 --- a/Firmware/communication/interface_uart.cpp +++ b/Firmware/communication/interface_uart.cpp @@ -87,7 +87,7 @@ static void uart_server_thread(void * ctx) { }; } -void serve_on_uart() { +void start_uart_server() { // DMA is set up to recieve in a circular buffer forever. // We dont use interrupts to fetch the data, instead we periodically read // data out of the circular buffer into a parse buffer, controlled by a state machine diff --git a/Firmware/communication/interface_uart.h b/Firmware/communication/interface_uart.h index 8ad71c19..a7a291f0 100644 --- a/Firmware/communication/interface_uart.h +++ b/Firmware/communication/interface_uart.h @@ -12,7 +12,7 @@ extern "C" { extern osThreadId uart_thread; -void serve_on_uart(void); +void start_uart_server(void); #ifdef __cplusplus } diff --git a/Firmware/communication/interface_usb.cpp b/Firmware/communication/interface_usb.cpp index 142765c2..c0194ec5 100644 --- a/Firmware/communication/interface_usb.cpp +++ b/Firmware/communication/interface_usb.cpp @@ -117,7 +117,7 @@ void usb_process_packet(uint8_t *buf, uint32_t len, uint8_t endpoint_pair) { osSemaphoreRelease(sem_usb_rx); } -void serve_on_usb() { +void start_usb_server() { // Start USB communication thread osThreadDef(usb_server_thread_def, usb_server_thread, osPriorityNormal, 0, 512); usb_thread = osThreadCreate(osThread(usb_server_thread_def), NULL); diff --git a/Firmware/communication/interface_usb.h b/Firmware/communication/interface_usb.h index b246a752..f8b11ee0 100644 --- a/Firmware/communication/interface_usb.h +++ b/Firmware/communication/interface_usb.h @@ -22,7 +22,7 @@ typedef struct { extern USBStats_t usb_stats_; void usb_process_packet(uint8_t *buf, uint32_t len, uint8_t endpoint_pair); -void serve_on_usb(void); +void start_usb_server(void); #ifdef __cplusplus } diff --git a/tools/odrive/enums.py b/tools/odrive/enums.py index 52b1e610..c492f9a0 100644 --- a/tools/odrive/enums.py +++ b/tools/odrive/enums.py @@ -27,7 +27,7 @@ MOTOR_TYPE_HIGH_CURRENT = 0 #MOTOR_TYPE_LOW_CURRENT = 1 MOTOR_TYPE_GIMBAL = 2 -CTRL_MODE_VOLTAGE_CONTROL = 0, -CTRL_MODE_CURRENT_CONTROL = 1, -CTRL_MODE_VELOCITY_CONTROL = 2, +CTRL_MODE_VOLTAGE_CONTROL = 0 +CTRL_MODE_CURRENT_CONTROL = 1 +CTRL_MODE_VELOCITY_CONTROL = 2 CTRL_MODE_POSITION_CONTROL = 3 From fac45e7bf045948ce1598dab48d21c7496f5592c Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Wed, 6 Jun 2018 00:10:34 -0700 Subject: [PATCH 214/215] DFU check for hw version --- tools/odrive/dfu.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/odrive/dfu.py b/tools/odrive/dfu.py index 0f7b458d..015b05e6 100755 --- a/tools/odrive/dfu.py +++ b/tools/odrive/dfu.py @@ -285,6 +285,11 @@ def update_device(device, firmware, logger, cancellation_token): hw_version_variant = device.hw_version_variant if hasattr(device, 'hw_version_variant') else 0 hw_version = (hw_version_major, hw_version_minor, hw_version_variant) + if hw_version < (3, 5, 0): + print("Warning: DFU mode is not supported on ODrives earlier than v3.5 unless you perform a hardware mod.") + if not odrive.utils.yes_no_prompt("Do you still want to continue?", False): + raise OperationAbortedException() + fw_version_major = device.fw_version_major if hasattr(device, 'fw_version_major') else 0 fw_version_minor = device.fw_version_minor if hasattr(device, 'fw_version_minor') else 0 fw_version_revision = device.fw_version_revision if hasattr(device, 'fw_version_revision') else 0 From 8cc735aeac7345205194ef254a2a6e47e8a51a18 Mon Sep 17 00:00:00 2001 From: Oskar Weigl Date: Wed, 6 Jun 2018 00:18:25 -0700 Subject: [PATCH 215/215] fix usage of GPIO_5 for ODrive v3.2 --- Firmware/Board/v3/Src/gpio.c | 4 ++++ Firmware/MotorControl/main.cpp | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/Firmware/Board/v3/Src/gpio.c b/Firmware/Board/v3/Src/gpio.c index 4e0c1d41..cfd2ffc9 100644 --- a/Firmware/Board/v3/Src/gpio.c +++ b/Firmware/Board/v3/Src/gpio.c @@ -283,7 +283,9 @@ GPIO_TypeDef* get_gpio_port_by_pin(uint16_t GPIO_pin){ case 2: return GPIO_2_GPIO_Port; break; case 3: return GPIO_3_GPIO_Port; break; case 4: return GPIO_4_GPIO_Port; break; +#ifdef GPIO_5_GPIO_Port case 5: return GPIO_5_GPIO_Port; break; +#endif default: return GPIO_1_GPIO_Port; } } @@ -294,7 +296,9 @@ uint16_t get_gpio_pin_by_pin(uint16_t GPIO_pin){ case 2: return GPIO_2_Pin; break; case 3: return GPIO_3_Pin; break; case 4: return GPIO_4_Pin; break; +#ifdef GPIO_5_Pin case 5: return GPIO_5_Pin; break; +#endif default: return GPIO_1_Pin; } } diff --git a/Firmware/MotorControl/main.cpp b/Firmware/MotorControl/main.cpp index 4801b039..b0ac1f7e 100644 --- a/Firmware/MotorControl/main.cpp +++ b/Firmware/MotorControl/main.cpp @@ -107,6 +107,7 @@ int odrive_main(void) { // Load persistent configuration (or defaults) load_configuration(); +#if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 3 if (board_config.enable_i2c_instead_of_can) { // Set up the direction GPIO as input GPIO_InitTypeDef GPIO_InitStruct; @@ -126,9 +127,9 @@ int odrive_main(void) { i2c_stats_.addr |= HAL_GPIO_ReadPin(I2C_A1_PORT, I2C_A1_PIN) != GPIO_PIN_RESET ? 0x2 : 0; i2c_stats_.addr |= HAL_GPIO_ReadPin(I2C_A2_PORT, I2C_A2_PIN) != GPIO_PIN_RESET ? 0x4 : 0; MX_I2C1_Init(i2c_stats_.addr); - } else { + } else +#endif MX_CAN1_Init(); - } // Init general user ADC on some GPIOs. GPIO_InitTypeDef GPIO_InitStruct; @@ -142,7 +143,7 @@ int odrive_main(void) { HAL_GPIO_Init(GPIO_3_GPIO_Port, &GPIO_InitStruct); GPIO_InitStruct.Pin = GPIO_4_Pin; HAL_GPIO_Init(GPIO_4_GPIO_Port, &GPIO_InitStruct); -#if HW_VERSION_MAJOR == 3 && HW_VERSION_MAJOR >= 5 +#if HW_VERSION_MAJOR == 3 && HW_VERSION_MINOR >= 5 GPIO_InitStruct.Pin = GPIO_5_Pin; HAL_GPIO_Init(GPIO_5_GPIO_Port, &GPIO_InitStruct); #endif