├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── Documentation ├── API Backend │ ├── Getting started │ │ └── Core mechanics.md │ ├── Motion │ │ ├── Configuration APIs.md │ │ └── Motion APIs.md │ └── Project goals and discussion.md └── API.md ├── LICENSE ├── README.md ├── controller-firmware ├── Vivado │ ├── .gitignore │ ├── autogen_sources │ │ └── controller.v │ ├── constraints │ │ └── system.xdc │ ├── controller_firmware.tcl │ ├── controller_firmware │ │ └── controller_firmware_top.xsa │ ├── controller_firmware_top.sv │ ├── controller_firmware_top.xsa │ ├── convert.bif │ ├── create_project.tcl │ ├── export bit.bin.tcl │ └── save_project.tcl └── python │ ├── .gitignore │ ├── config │ └── hardware configs │ │ └── hardware_config.yaml │ ├── generators │ └── dma_instruction_compiler.py │ ├── requirements.txt │ └── src │ ├── axi.py │ ├── biquad.py │ ├── cascaded_PI_controller.py │ ├── controller.py │ ├── convergent_round.py │ ├── dma.py │ ├── drive_serial_port.py │ ├── em_serial_controller.py │ ├── em_serial_port.py │ ├── fanuc_encoder.py │ ├── global_timer.py │ ├── gpio_node.py │ ├── i2c.py │ ├── interface_cards │ └── serial_interface.py │ ├── main.py │ ├── registers.py │ ├── registers2.py │ ├── sandbox │ ├── biquad test.py │ ├── current_control.py │ ├── fanuc_encoder_rs422.csv │ ├── fanuc_encoder_sim.py │ ├── instructions.py │ ├── test.py │ └── test1.py │ ├── scope.py │ ├── serial_controller.py │ ├── shift_dma.py │ ├── sin_cos_lookup.py │ └── testing_block.py ├── controller-software ├── config │ └── electrical │ │ ├── connectors.py │ │ ├── motor_drives │ │ └── em_hvsd.py │ │ ├── motors │ │ ├── motor_linear.py │ │ └── motor_rotary.py │ │ ├── parameters.py │ │ ├── power │ │ ├── line_reactor.py │ │ └── power_sources.py │ │ ├── test.py │ │ └── user_params.py ├── core │ ├── .gitignore │ ├── .vscode │ │ ├── controller-software-core.code-workspace │ │ ├── launch.json │ │ └── tasks.json │ ├── CMakeLists.txt │ ├── CMakePresets.json │ ├── cmake │ │ └── zynq_debug.cmake │ ├── controller │ │ ├── api_drivers │ │ │ ├── api_objects.h │ │ │ ├── calls │ │ │ │ ├── base_call.h │ │ │ │ ├── machine_state.h │ │ │ │ └── print_uint32.h │ │ │ ├── controller_api.h │ │ │ ├── shared_mem.h │ │ │ ├── test_api │ │ │ ├── test_api.cpp │ │ │ ├── test_controller │ │ │ ├── test_controller.cpp │ │ │ ├── test_controller.exe │ │ │ └── web_api.h │ │ ├── fpga_module_drivers │ │ │ ├── inc │ │ │ │ ├── em_serial_controller.h │ │ │ │ ├── fanuc_encoders.h │ │ │ │ ├── global_timers.h │ │ │ │ └── serial_interface_card.h │ │ │ └── src │ │ │ │ ├── em_serial_controller.cpp │ │ │ │ ├── fanuc_encoders.cpp │ │ │ │ ├── global_timers.cpp │ │ │ │ └── serial_interface_card.cpp │ │ ├── inc │ │ │ ├── controller.h │ │ │ ├── fpga_instructions.h │ │ │ ├── fpga_interface.h │ │ │ ├── fpga_module_driver_factory.h │ │ │ ├── fpga_module_manager.h │ │ │ ├── json.hpp │ │ │ └── register_helper.h │ │ ├── nodes │ │ │ ├── base_node.h │ │ │ ├── generic_nodes │ │ │ │ ├── bool_constant.h │ │ │ │ ├── bool_print_cout.h │ │ │ │ ├── logic_and.h │ │ │ │ ├── logic_not.h │ │ │ │ └── nothing_delay.h │ │ │ ├── global_variables │ │ │ │ └── base_global_variable.h │ │ │ ├── node_core.h │ │ │ ├── node_factory.h │ │ │ ├── node_io.h │ │ │ └── node_network.h │ │ └── src │ │ │ ├── fpga_interface.cpp │ │ │ ├── fpga_module_manager.cpp │ │ │ ├── main.cpp │ │ │ ├── readme.md │ │ │ └── register_helper.cpp │ ├── devices │ │ └── em_hvsd │ │ │ └── config.yaml │ └── zynq_files │ │ └── controller │ │ ├── bin │ │ └── controller_core │ │ └── config │ │ └── fpga_configs │ │ ├── bit_files │ │ └── bitfile.bit.bin │ │ └── controller_config.json └── web │ ├── api │ ├── api_helper.h │ ├── api_test.py │ ├── binding.gyp │ ├── controller_interface.cpp │ ├── main.js │ ├── openrpc-methods.js │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── index.html │ ├── litegraph.css │ └── litegraph.js │ └── users.json ├── em-os ├── .gitignore ├── build_pack_image.sh ├── build_run_qemu.sh └── project-spec │ ├── attributes │ ├── configs │ ├── .statistics │ ├── busybox │ │ └── inetd.conf │ ├── config │ ├── configs │ │ ├── Kconfig │ │ └── Kconfig.syshw │ ├── gen-machineconf.log.old │ ├── init-ifupdown │ │ └── interfaces │ ├── plnx_syshw_data │ ├── plnxtool.conf │ ├── rootfs.wks │ ├── rootfs_config │ ├── rootfsconfigs │ │ ├── Kconfig │ │ ├── Kconfig.part │ │ ├── Kconfig.user │ │ └── user-rootfsconfig │ ├── systemd-conf │ │ └── wired.network │ └── zynq-generic-7z020.conf │ ├── hw-description │ ├── controller_firmware_top.bit │ ├── metadata │ ├── ps7_init.c │ ├── ps7_init.h │ ├── ps7_init.html │ ├── ps7_init.tcl │ ├── ps7_init_gpl.c │ ├── ps7_init_gpl.h │ └── system.xsa │ └── meta-user │ ├── COPYING.MIT │ ├── README │ ├── conf │ ├── layer.conf │ ├── petalinuxbsp.conf │ └── user-rootfsconfig │ ├── meta-xilinx-tools │ └── recipes-bsp │ │ └── uboot-device-tree │ │ ├── files │ │ └── system-user.dtsi │ │ └── uboot-device-tree.bbappend │ ├── recipes-bsp │ ├── device-tree │ │ ├── device-tree-sdt.inc │ │ ├── device-tree.bbappend │ │ └── files │ │ │ └── system-user.dtsi │ └── u-boot │ │ ├── files │ │ ├── bsp.cfg │ │ ├── platform-top.h │ │ ├── user_2024-10-27-00-48-00.cfg │ │ └── user_2024-11-11-00-54-00.cfg │ │ └── u-boot-xlnx_%.bbappend │ └── recipes-kernel │ └── linux │ ├── linux-xlnx │ ├── bsp.cfg │ ├── user_2024-11-05-04-03-00.cfg │ └── user_2024-11-12-22-53-00.cfg │ └── linux-xlnx_%.bbappend ├── petalinux └── controller_firmware_top.xsa └── setup_subst.bat /.gitignore: -------------------------------------------------------------------------------- 1 | /controller-firmware/src/autogen_sources 2 | *.jou 3 | *.log 4 | /.Xil 5 | /controller-firmware/Vivado/.Xil 6 | *.pyc 7 | /controller-software/config/venv 8 | /controller-software/web/node_modules/.bin 9 | /controller-software/web/node_modules 10 | 11 | controller-software/* 12 | !controller-software/config/ 13 | !controller-software/core/ 14 | !controller-software/vitis/ 15 | !controller-software/web/ 16 | /controller-software/web/build 17 | controller-software/core/api_drivers/test_controller 18 | 19 | .vscode/settings.json 20 | controller-firmware/python/pyvenv.cfg 21 | *.vcd 22 | controller-software/core/zynq_files/controller/config/fpga_configs/bit_files/bitfile.bit.bin 23 | controller-software/core/zynq_files/controller/config/fpga_configs/controller_config.json 24 | controller-software/core/zynq_files/controller/bin/controller_core 25 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Launch Program", 8 | "skipFiles": ["/**"], 9 | "program": "${workspaceFolder}/controller-software/web/main.js" // Replace with your entry point file 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "options": { 4 | "env": { 5 | "xilinx_path": "/home/excessive/Xilinx", 6 | "xilinx_version": "2023.1", 7 | "petalinux_path": "/home/excessive/PetaLinux" 8 | } 9 | }, 10 | 11 | "tasks": [ 12 | { 13 | "label": "create subst path vivado", 14 | "type": "shell", 15 | "windows": { "command": "if not exist S: subst S: ${workspaceFolder}/controller-firmware"}, 16 | "linux": { "command": "echo ''"}, 17 | "problemMatcher": [] 18 | }, 19 | { 20 | "label": "create subst path vitis", 21 | "type": "shell", 22 | "windows": { "command": "if not exist T: subst T: ${workspaceFolder}/controller-software"}, 23 | "linux": { "command": "echo ''"}, 24 | "problemMatcher": [] 25 | }, 26 | { 27 | "label": "setup python venv", 28 | "type": "shell", 29 | "command": "if not exist venv python -m venv venv", 30 | "options": { 31 | "cwd": "${workspaceFolder}/controller-firmware/python/" 32 | }, 33 | "problemMatcher": [] 34 | }, 35 | { 36 | "label": "install python packages", 37 | "type": "shell", 38 | "command": "venv\\Scripts\\activate.bat && pip install -r requirements.txt", 39 | "options": { 40 | "cwd": "${workspaceFolder}/controller-firmware/python/" 41 | }, 42 | "dependsOn": ["setup python venv"], 43 | "problemMatcher": [] 44 | }, 45 | { 46 | "label": "create vivado project", 47 | "type": "shell", 48 | "windows": { "command": "${env:xilinx_path}/vivado/${env:xilinx_version}/settings64.bat && if not exist S:vivado/controller_firmware vivado -mode batch -source ${workspaceFolder}/controller-firmware/vivado/create_project.tcl -tclargs S:Vivado"}, 49 | "linux": { "command": "source ${xilinx_path}/Vivado/${xilinx_version}/settings64.sh && if [ ! -d ${workspaceFolder}/controller-firmware/Vivado/controller-firmware ]; then vivado -mode batch -source ${workspaceFolder}/controller-firmware/Vivado/create_project.tcl -tclargs ${workspaceFolder}/controller-firmware/Vivado; fi"}, 50 | "problemMatcher": [], 51 | "dependsOn": ["create subst path vivado"], 52 | }, 53 | // TODO: add vitis project creation 54 | // { 55 | // "label": "create vitis project", 56 | // "type": "shell", 57 | // "windows": { "command": "${env:xilinx_path}/vitis/${env:xilinx_version}/settings64.bat && if not exist T:vitis/controller_firmware vivado -mode batch -source ${workspaceFolder}/controller-firmware/vitis/create_project.tcl -tclargs T:Vitis"}, 58 | // "linux": { "command": "source ${xilinx_path}/Vitis/${xilinx_version}/settings64.sh && if [ ! -d ${workspaceFolder}/controller-software/vitis/osrc/logs ]; then xsct ${workspaceFolder}/controller-software/vitis/create_project.tcl ${workspaceFolder}; fi"}, 59 | // "problemMatcher": [], 60 | // "dependsOn": ["create subst path vitis"], 61 | // }, 62 | { 63 | "label": "save vivado project", 64 | "type": "shell", 65 | "command": "c:/xilinx/vivado/2023.1/settings64.bat && vivado -mode batch -source ${workspaceFolder}/controller-firmware/vivado/save_project.tcl", // TODO: make this vivado path easier to change 66 | "problemMatcher": [], 67 | "dependsOn": ["create subst path"] 68 | }, 69 | { 70 | "label": "setup project", 71 | "dependsOn": ["create vivado project", "install python packages"], 72 | "problemMatcher": [] 73 | } 74 | //TODO: add tasks for copying and installing petalinux files (.xsa file, etc.) 75 | ] 76 | } -------------------------------------------------------------------------------- /Documentation/API Backend/Getting started/Core mechanics.md: -------------------------------------------------------------------------------- 1 | 2 | ## Websocket 3 | 4 | Connection to the API is established through a websocket connection to the device. Once established users can make JSON RPC 2.0 requests for motion and configuration requests 5 | 6 | ## Basic request format 7 | 8 | TBD -------------------------------------------------------------------------------- /Documentation/API Backend/Motion/Configuration APIs.md: -------------------------------------------------------------------------------- 1 | 2 | ### Machine setup 3 | Some format for specifying number of axis, robot shape, weight distribution, and kinematics 4 | 5 | ### IO Config 6 | specify names and properties of GPIO elements of machine 7 | 8 | ### Safety 9 | Specify zone limits and velocity limits -------------------------------------------------------------------------------- /Documentation/API Backend/Motion/Motion APIs.md: -------------------------------------------------------------------------------- 1 | 2 | # Jogging 3 | 4 | API commands for Joggin the robot at {{JogSpeed}} rotations per minute (TODO: define jog speed) 5 | 6 | Because of the stream like nature of jogging, jogging requests are sent as notifications without id. On a success the client will not receive any response. On a failure the API will send out a {{MotionError}} notification. Jogging requests should be sent at a regular frequency in order to maintain the current motion. When no jogging message has not been received for {{JogFrequency}} (default 100ms) milliseconds, the motion controller will immediatly stop. 7 | 8 | Be advised that high latency connection can result in motor stutter. Increasing the {{JogFrequency}} can fix this but will result in a more delayed stop time. 9 | 10 | ### Raw joint movements 11 | 12 | This is a notification command. There should not be a ID in this request. 13 | ``` 14 | {"method": "jogJoint", "params": [jointIndex, direction]} 15 | ``` 16 | Params: 17 | jointIndex: 0 based index for the joint. 18 | direction: 1 for positive direction, 0 for negative direction 19 | 20 | Result: None 21 | Error: 22 | {{MotionError}} notification will be sent out if a jog command could not be followed. 23 | Example: 24 | ``` 25 | {"method": "jogJoint", "params": [0, 1]} 26 | ``` 27 | Robot joint 0 should go in the positive direction at {{JogSpeed}} 28 | 29 | ### Tool frame jogging 30 | 31 | This is a notification command. There should not be a ID in this request. 32 | ``` 33 | {"method": "jogTool", "params": [toolIndex, vector]} 34 | ``` 35 | Params: 36 | toolIndex: 0 based index for the tool frame. 37 | vector: Array designating the direction vecor in XYZ format (TODO: this is an assumption on the capabilites from the motion planning team. Change this if needed) 38 | 39 | Result: None 40 | Error: 41 | {{MotionError}} notification will be sent out if a jog command could not be followed. 42 | Example: 43 | ``` 44 | {"method": "jogTool", "params": [0, [0,1,0]]} 45 | ``` 46 | Robot should go forward in the Y direction with repect to the tool frame 47 | 48 | ### Workspace frame jogging 49 | This is a notification command. There should not be a ID in this request. 50 | ``` 51 | {"method": "jogWorkspace", "params": [workspaceIndex, vector]} 52 | ``` 53 | Params: 54 | workspaceIndex: 0 based index for the workspace frame. 55 | vector: Array designating the direction vecor in XYZ format (TODO: this is an assumption on the capabilites from the motion planning team. Change this if needed) 56 | 57 | Result: None 58 | Error: 59 | {{MotionError}} notification will be sent out if a jog command could not be followed. 60 | 61 | Example: 62 | ``` 63 | {"method": "jogWorkspace", "params": [0, [0,1,0]]} 64 | ``` 65 | Robot should go forward in the Y direction with repect to the workspace frame -------------------------------------------------------------------------------- /Documentation/API Backend/Project goals and discussion.md: -------------------------------------------------------------------------------- 1 | This document should be used as a guide for developers to understand the overall direction of this module. Later as things kick off this document can probably be removed 2 | 3 | ## Dependencies 4 | - **[MinnowServer](https://github.com/RealTimeLogic/MinnowServer)** 5 | - Handle TCP/IP transmission 6 | - Maintains lower layer web socket needs 7 | - TBD 8 | 9 | ## Proposed transmission protocols 10 | 11 | ### JSON RPC 12 | Pros: 13 | - Human readable 14 | - Easy request response structure 15 | - Platform agnostic 16 | Cons: 17 | - Considerable amount of extra data being transferred 18 | - Slower serialization and deserialization steps 19 | 20 | ### Raw bytecode 21 | Pros: 22 | - Very small footprint 23 | - Faster serialization on the server side 24 | Cons: 25 | - Not human readable 26 | 27 | ## Some things to think about 28 | 29 | - Jogging: 30 | - Jog button pressed vs held. Is multiple messages being sent while button held or will we have a jog start and jog stop command? 31 | - General requirement should be when I let go of the button for jogging then the arm stops as well. No delay because the robot is trying to execute a series of move commands. -------------------------------------------------------------------------------- /Documentation/API.md: -------------------------------------------------------------------------------- 1 | # API Definition 2 | 3 | For this document, a parameter represents some value on the controller. 4 | It may be read-only, writable in different states, continuously-updating etc. 5 | It may represent a configuration item, a file on disk, a sensor reading, a control input etc. 6 | 7 | ## Custom types 8 | 9 | ### Kinematics 10 | 11 | ```yaml 12 | joint_state: 13 | position: 14 | type: float64 15 | unit: m (linear joints) or rad (angular joints) 16 | velocity: 17 | type: float64 18 | unit: m/s (linear joints) or rad/s (angular joints) 19 | effort: 20 | type: float64 21 | unit: N (linear joints) or Nm (angular joints) 22 | ``` 23 | 24 | ### State 25 | 26 | ```yaml 27 | controller_state: 28 | mode: 29 | type: enum 30 | enum_options: 31 | - offline 32 | - configure 33 | - e-stop 34 | - manual-control 35 | - active-pause 36 | - active-run 37 | ``` 38 | 39 | ### ... 40 | 41 | Other types will be required... 42 | 43 | ## Generic API 44 | 45 | ### Parameters 46 | 47 | Interact with a parameter at a single point in time. 48 | 49 | ```yaml 50 | parameter_list: 51 | request: 52 | namespace: 53 | type: string 54 | description: Fetch all parameters underneath this namespace 55 | recursive: 56 | type: bool 57 | default: False 58 | description: If true, fetch nested parameters as well 59 | response: 60 | parameters: 61 | type: string[] 62 | description: List of all parameters in this namespace 63 | ``` 64 | 65 | ```yaml 66 | parameter_info: 67 | request: 68 | name: 69 | type: string 70 | description: Full name of parameter including namespace (eg /joint/1/state) 71 | response: 72 | type: 73 | type: string 74 | description: Name of parameter type 75 | description: 76 | type: string 77 | description: Human-readable description of the parameter's purpose 78 | read_only: 79 | type: bool 80 | description: Is the parameter read-only? 81 | permission_level: 82 | type: int32 83 | description: Client permission level required to change this value 84 | states: 85 | type: controller_state[] 86 | description: States in which this value may be changed 87 | ``` 88 | 89 | ```yaml 90 | parameter_fetch: 91 | request: 92 | name: 93 | type: string 94 | description: Name of parameter to fetch, including namespace 95 | response: 96 | data: 97 | type: Any 98 | description: Current parameter value, type as specified by a parameter_info call 99 | ``` 100 | 101 | ```yaml 102 | parameter_set: 103 | request: 104 | name: 105 | type: string 106 | value: 107 | type: Any 108 | description: New parameter value, type as specified by a parameter_info call 109 | response: 110 | ok: 111 | type: bool 112 | description: True if the update succeeded 113 | ``` 114 | 115 | ### Streaming 116 | 117 | Request that the controller pushes a stream of messages whenever a given parameter changes. 118 | 119 | ```yaml 120 | subscribe: 121 | request: 122 | name: 123 | type: string 124 | description: Full name of parameter including namespace to subscribe to 125 | rate_limit: 126 | type: int32 127 | description: Maximum number of values to send per minute. Depending on the parameter, excess will be dropped silently or an average will be created. 128 | response: 129 | null 130 | ``` 131 | 132 | ```yaml 133 | list_subscriptions: 134 | request: 135 | null 136 | response: 137 | subscriptions: 138 | type: string[] 139 | description: List of parameters currently subscribed to 140 | ``` 141 | 142 | ```yaml 143 | unsubscribe: 144 | request: 145 | name: 146 | type: string 147 | response: 148 | null 149 | ``` 150 | 151 | ### Control streams 152 | 153 | Indicate to the controller that you would like to push a stream of set-value messages to it. 154 | 155 | ```yaml 156 | init_control_stream: 157 | request: 158 | name: 159 | type: string 160 | description: Full name of parameter including namespace to open a control stream for 161 | response: 162 | ok: 163 | type: bool 164 | ``` 165 | 166 | ```yaml 167 | list_control_streams: 168 | request: 169 | null 170 | response: 171 | subscriptions: 172 | type: string[] 173 | description: List of parameters with active control streams 174 | ``` 175 | 176 | ```yaml 177 | cancel_control_stream: 178 | request: 179 | name: 180 | type: string 181 | response: 182 | null 183 | ``` 184 | 185 | ## Parameters 186 | 187 | Some example parameters. 188 | 189 | ```yaml 190 | /joint/state: 191 | description: State of all joints on the controller 192 | type: joint_state[] 193 | read_only: true 194 | /joint/*/controller/current: 195 | description: Joint PID current controller proportional value 196 | type: pid_value 197 | /joint/${joint_index}/jog: 198 | description: Jog input for the given joint. 199 | type: joint_control 200 | permission_level: 3 201 | states: manual-control 202 | /filesystem/download-file/${file_path}: 203 | description: Get the contents of a given filee 204 | type: string 205 | permission_level: 1 206 | ``` 207 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # controller-software 2 | 3 | Firmware for the motion controller. This project consists of multiple sections: 4 | - Controller software: The actual EM software that runs on the ZYNQ 5 | - Controller Firmware: All files related to generating FPGA configs that the software can later load 6 | - em-os: Petalinux project for building a linux image for the ZYNQ to boot 7 | 8 | ## Initial project setup 9 | 10 | ### Requirements 11 | - [Visual Studio Code](https://code.visualstudio.com/) 12 | - [Python 3.12](https://www.python.org/downloads/release/python-3123/) 13 | - [Vivado 2023.1](https://www.xilinx.com/support/download/index.html/content/xilinx/en/downloadNav/vivado-design-tools/2023-1.html) 14 | - [Surfer](https://surfer-project.org/) (optional, used to view HDL sim trace files) 15 | 16 | 17 | ### Setup (windows/linux) 18 | #### 1. Run the "setup project" task in VS Code (Terminal -> Run Task... -> setup project) 19 | This will: 20 | - Run subst to create a new drive leading to vivado files, this is needed to keep path lengths short (windows only) 21 | - Create the python virtual environment 22 | - Install required python packages 23 | - Create the vivado project 24 | - TODO: Create the vitis project 25 | 26 | 27 | 28 | ## Commiting changes 29 | ### Vivado 30 | #### Run the "save vivado project" task in VS Code (Terminal -> Run Task... -> save vivado project) 31 | This will: 32 | - Save the vivado project to a .tcl file which is in the repo and can be committed 33 | -------------------------------------------------------------------------------- /controller-firmware/Vivado/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | * 3 | 4 | # But not these files... 5 | !.gitignore 6 | !component.xml 7 | !.project 8 | !.cproject 9 | !constraints.xdc 10 | !*.xci 11 | !*.v 12 | !*.sv 13 | !*.svh 14 | !*.vh 15 | !*.vhd 16 | !*.tcl 17 | !*.wcfg 18 | !*.h 19 | !*.cc 20 | !*.cpp 21 | !*.c 22 | !*.inc 23 | !*.py 24 | !*.xdc 25 | !*.md 26 | !*.xsa 27 | !*.bif 28 | 29 | # ...even if they are in subdirectories 30 | !*/ 31 | 32 | # Except for xilinx auto-generated files 33 | /**/*.srcs/ 34 | /**/*.cache/ 35 | /**/*.hw/ 36 | /**/*.ip_user_files/ 37 | /**/*.runs/ 38 | /**/*.sim/ 39 | /**/*_BSP*/ 40 | /**/*_HW*/ 41 | /**/.Xil/ 42 | /**/.metadata/ 43 | /**/RemoteSystemsTempFiles/ 44 | /**/doc/ 45 | /**/webtalk/ 46 | /**/hdl/ 47 | /**/demo_tb/ 48 | /**/misc/ 49 | /**/bd/ 50 | -------------------------------------------------------------------------------- /controller-firmware/Vivado/controller_firmware/controller_firmware_top.xsa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExcessiveMotion/controller-software/0503bb292ecb54b9c41ecbcdc22a93bbeb4c36b0/controller-firmware/Vivado/controller_firmware/controller_firmware_top.xsa -------------------------------------------------------------------------------- /controller-firmware/Vivado/controller_firmware_top.xsa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExcessiveMotion/controller-software/0503bb292ecb54b9c41ecbcdc22a93bbeb4c36b0/controller-firmware/Vivado/controller_firmware_top.xsa -------------------------------------------------------------------------------- /controller-firmware/Vivado/convert.bif: -------------------------------------------------------------------------------- 1 | the_ROM_image: 2 | { 3 | S:/Vivado/bitfile.bit 4 | } -------------------------------------------------------------------------------- /controller-firmware/Vivado/create_project.tcl: -------------------------------------------------------------------------------- 1 | 2 | set project_dir [lindex $argv 0] 3 | cd $project_dir 4 | 5 | source controller_firmware.tcl -------------------------------------------------------------------------------- /controller-firmware/Vivado/export bit.bin.tcl: -------------------------------------------------------------------------------- 1 | file copy -force S:/Vivado/controller_firmware/controller_firmware.runs/impl_1/controller_firmware_top.bit S:/Vivado/bitfile.bit 2 | 3 | exec bootgen -w -arch zynq -image S:/Vivado/convert.bif -process_bitstream bin -------------------------------------------------------------------------------- /controller-firmware/Vivado/save_project.tcl: -------------------------------------------------------------------------------- 1 | # Set the project name and file paths 2 | set project_name "controller_firmware" 3 | set project_path "S:/Vivado/controller_firmware" 4 | set export_script_path "S:/Vivado/controller_firmware.tcl" 5 | 6 | # Open the Vivado project 7 | open_project $project_path/$project_name.xpr 8 | 9 | # Export the project to a Tcl script 10 | write_project_tcl -force $export_script_path 11 | 12 | # Close the Vivado project 13 | close_project -------------------------------------------------------------------------------- /controller-firmware/python/.gitignore: -------------------------------------------------------------------------------- 1 | /venv 2 | /sim outputs -------------------------------------------------------------------------------- /controller-firmware/python/config/hardware configs/hardware_config.yaml: -------------------------------------------------------------------------------- 1 | hardware_slots: 2 | slot_a: 3 | #TODO: support more than one card type per slot? 4 | card_type: "serial interface card" 5 | card_options: 6 | # set which internal interfaces should be connected to each serial port. 7 | # TODO: support more than one internal interface per serial port? 8 | port_0: [fanuc_rs422_encoder] 9 | port_1: [serial_device] 10 | 11 | slot_b: 12 | 13 | slot_c: 14 | 15 | slot_d: 16 | 17 | fpga_blocks: # extra internal functions 18 | cascaded_PI_controller: 19 | count: 1 20 | parameters: # build parameters for the block when instantiating it 21 | instances: 1 22 | -------------------------------------------------------------------------------- /controller-firmware/python/generators/dma_instruction_compiler.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | class compiler: 6 | def __init__(self): 7 | self.high_level_instructions = [] 8 | self.output = [] 9 | 10 | def copy(self, src_node, src_addr, dest_node, dest_addr): 11 | self.high_level_instructions.append([src_node, dest_node, "copy"]) 12 | def compile(): 13 | output = [] 14 | return output -------------------------------------------------------------------------------- /controller-firmware/python/requirements.txt: -------------------------------------------------------------------------------- 1 | amaranth==0.5.1 2 | amaranth-yosys==0.40.0.0.post99 3 | contourpy==1.3.0 4 | cycler==0.12.1 5 | fonttools==4.53.1 6 | importlib_resources==6.4.4 7 | Jinja2==3.1.4 8 | jschon==0.11.1 9 | kiwisolver==1.4.7 10 | MarkupSafe==2.1.5 11 | matplotlib==3.9.2 12 | packaging==24.1 13 | pillow==10.4.0 14 | pyparsing==3.1.4 15 | python-dateutil==2.9.0.post0 16 | pyvcd==0.4.0 17 | rfc3986==2.0.0 18 | six==1.16.0 19 | wasmtime==24.0.0 20 | -------------------------------------------------------------------------------- /controller-firmware/python/src/biquad.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.sim import Simulator 3 | from amaranth.back import verilog 4 | from src.convergent_round import convergentRound 5 | import matplotlib.pyplot as plt 6 | 7 | 8 | class biquad_32(Elaboratable): 9 | """ 10 | high precision 32bit biquad IIR filter 11 | """ 12 | 13 | def __init__(self, signed_, constrainOutput_) -> None: 14 | 15 | """ 16 | signed: if true, use 32bit signed values for the input and output 17 | 18 | constrainOutput: force the output to be within the output range. Certain step inputs and coeff settings may cause the internal output value to exceed the output range, without counstrainOutput, the value will be left to wrap, but will exhibit the unchanged filter response 19 | """ 20 | 21 | self.signed = signed_ 22 | self.constrainOutput = constrainOutput_ 23 | 24 | # ports 25 | self.update = Signal() 26 | if(self.signed): 27 | self.input = Signal(shape=signed(32)) 28 | self.output = Signal(shape=signed(32)) 29 | else: 30 | self.input = Signal(32) 31 | self.output = Signal(32) 32 | 33 | self.updateDone = Signal() 34 | 35 | self.zeroCoeff_0 = Signal(shape=signed(36)) 36 | self.zeroCoeff_1 = Signal(shape=signed(36)) 37 | self.zeroCoeff_2 = Signal(shape=signed(36)) 38 | 39 | self.poleCoeff_1 = Signal(shape=signed(36)) 40 | self.poleCoeff_2 = Signal(shape=signed(36)) 41 | 42 | self.ports = [ 43 | self.update, 44 | self.updateDone, 45 | self.input, 46 | self.output, 47 | self.zeroCoeff_0, 48 | self.zeroCoeff_1, 49 | self.zeroCoeff_2, 50 | self.poleCoeff_1, 51 | self.poleCoeff_2 52 | ] 53 | 54 | # internal signals 55 | self.dn_0 = Signal(shape=signed(48)) 56 | self.dn_1 = Signal(shape=signed(48)) 57 | 58 | self.zeroAccum = Signal(shape=signed(48*2)) 59 | self.poleAccum = Signal(shape=signed(48*2)) 60 | 61 | self.state = Signal(range(5)) 62 | 63 | self.inputPrescaler = 2**32 64 | 65 | 66 | def elaborate(self, platform): 67 | m = Module() 68 | 69 | m.submodules.inputRound = self.inputRound = convergentRound(96, 96-32) 70 | m.submodules.outputRound = self.outputRound = convergentRound(96, 96-32) 71 | 72 | with m.If(self.update & self.state == 0): 73 | m.d.comb += self.inputRound.input.eq(self.inputPrescaler * self.input - self.dn_0 * self.poleCoeff_1 - self.dn_1 * self.poleCoeff_2) 74 | m.d.sync += self.state.eq(1) 75 | m.d.sync += self.updateDone.eq(0) 76 | 77 | with m.If(self.state == 1): 78 | m.d.sync += self.state.eq(2) 79 | m.d.comb += self.outputRound.input.eq(self.inputRound.output * self.zeroCoeff_0 + self.dn_0 * self.zeroCoeff_1 + self.dn_1 * self.zeroCoeff_2) 80 | m.d.sync += self.dn_0.eq(self.inputRound.output) 81 | m.d.sync += self.dn_1.eq(self.dn_0) 82 | 83 | with m.If(self.state == 2): 84 | 85 | if(self.constrainOutput): 86 | with m.If(self.outputRound.output >= 2**32): 87 | m.d.sync += self.output.eq(2**32-1) 88 | with m.Elif(self.outputRound.output < 0): 89 | m.d.sync += self.output.eq(0) 90 | with m.Else(): 91 | m.d.sync += self.output.eq(self.outputRound.output) 92 | else: 93 | m.d.sync += self.output.eq(self.outputRound.output) 94 | 95 | m.d.sync += self.updateDone.eq(1) 96 | m.d.sync += self.state.eq(Mux(self.update, 2, 0)) 97 | 98 | return m 99 | 100 | 101 | clock = int(100e6) 102 | dut = biquad_32(False, True) 103 | 104 | ENCODER_COUNT = 2**16 105 | 106 | times = [] 107 | filteredPos = [] 108 | realEncoder = [] 109 | integerEncoder = [] 110 | 111 | 112 | async def biquadBench(ctx): 113 | ctx.set(dut.zeroCoeff_0, int(0.00003536167187236639 * 2**32)) 114 | ctx.set(dut.zeroCoeff_1, int(0.00007072334374473279 * 2**32)) 115 | ctx.set(dut.zeroCoeff_2, int(0.00003536167187236639 * 2**32)) 116 | 117 | ctx.set(dut.poleCoeff_1, int(-1.9848607704104781 * 2**32)) 118 | ctx.set(dut.poleCoeff_2, int(0.9850022170979679 * 2**32)) 119 | 120 | encoderCountReal = 0.0 121 | encoderCountInteger = 0 122 | 123 | for i in range(4000): 124 | 125 | # update encoder 126 | encoderCountReal += 2**2 127 | encoderCountInteger = round(encoderCountReal, 0) 128 | if(encoderCountInteger > ENCODER_COUNT-1): 129 | encoderCountInteger -= ENCODER_COUNT 130 | encoderCountReal -= ENCODER_COUNT 131 | elif(encoderCountInteger < 0): 132 | encoderCountInteger += ENCODER_COUNT 133 | encoderCountReal += ENCODER_COUNT 134 | 135 | # if(i == 200): 136 | # encoderCountReal += ENCODER_COUNT -50 137 | 138 | # if(i == 400): 139 | # encoderCountReal += ENCODER_COUNT -50 140 | 141 | ctx.set(dut.input, int(int(encoderCountInteger) * (2**32 / ENCODER_COUNT))) 142 | ctx.set(dut.update, 1) 143 | await ctx.tick() 144 | ctx.set(dut.update, 0) 145 | 146 | while(not ctx.get(dut.updateDone)): 147 | await ctx.tick() 148 | 149 | times.append(i) 150 | filteredPos.append(ctx.get(dut.output)) 151 | realEncoder.append(encoderCountReal * (2**32 / ENCODER_COUNT)) 152 | integerEncoder.append(int(encoderCountInteger) * (2**32 / ENCODER_COUNT)) 153 | 154 | 155 | 156 | if __name__ == "__main__": 157 | 158 | sim = Simulator(dut) 159 | sim.add_clock(1/clock) 160 | sim.add_testbench(biquadBench) 161 | with sim.write_vcd("biquad_32.vcd"): 162 | sim.run() 163 | 164 | plt.plot(times, filteredPos) 165 | plt.plot(times, realEncoder) 166 | plt.plot(times, integerEncoder) 167 | plt.show() 168 | 169 | # if (True): # export 170 | # top = biquad_32(int(100e6)) 171 | # with open("controller-firmware/src/amaranth sources/biquad_32.v", "w") as f: 172 | # f.write(verilog.convert(top, name="biquad_32", ports=top.ports)) -------------------------------------------------------------------------------- /controller-firmware/python/src/convergent_round.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.sim import Simulator 3 | import numpy as np 4 | from amaranth.back import verilog 5 | import math 6 | 7 | 8 | class convergentRound(Elaboratable): 9 | """ 10 | signed rounding with the lowest bias 11 | 12 | requires 1 clock cycle 13 | """ 14 | 15 | def __init__(self, input_width, output_width): 16 | assert input_width > output_width, "Input width must be greater than output width" 17 | self.input = Signal(signed(input_width)) 18 | self.LastInput = Signal(signed(input_width)) 19 | self.output = Signal(signed(output_width)) 20 | self.done = Signal(reset=1) 21 | 22 | def elaborate(self, platform): 23 | m = Module() 24 | 25 | # Calculate the bit-width difference 26 | shift = len(self.input) - len(self.output) 27 | 28 | # Extract the rounding bits 29 | round_bit = self.input[shift - 1] 30 | 31 | sticky_bit = self.input[:shift - 1].bool() 32 | 33 | # Determine if the input is negative 34 | is_negative = self.input[-1] 35 | 36 | self.rounding = Signal(signed(len(self.output))) 37 | 38 | with m.If(self.input[shift:-1].all() & ~is_negative): # prevent overflow due to round-up when near max positive value 39 | m.d.comb += self.rounding.eq(self.input[shift:]) 40 | 41 | with m.Elif(round_bit & sticky_bit): # round up 42 | m.d.comb += self.rounding.eq(self.input[shift:] + 1) 43 | 44 | with m.Elif(~round_bit): # round down 45 | m.d.comb += self.rounding.eq(self.input[shift:]) 46 | 47 | with m.Elif(round_bit & ~sticky_bit): # round to even 48 | m.d.comb += self.rounding.eq(self.input[shift:] + self.input[shift]) 49 | 50 | m.d.sync += self.output.eq(self.rounding) 51 | m.d.sync += self.LastInput.eq(self.input) 52 | 53 | with m.If(self.input != self.LastInput): 54 | m.d.comb += self.done.eq(0) 55 | with m.Else(): 56 | m.d.comb += self.done.eq(1) 57 | 58 | return m 59 | 60 | 61 | 62 | dut = convergentRound(8, 4) # round a 8bit number to 4 bit 63 | 64 | async def convergentRoundBench(ctx): 65 | 66 | bias = 0 67 | 68 | for i in range(-2**7+1, 2**7-1): 69 | ctx.set(dut.input, i) 70 | await ctx.tick().repeat(3) 71 | 72 | out = ctx.get(dut.output) 73 | 74 | print(i, out) 75 | await ctx.tick() 76 | 77 | 78 | 79 | if __name__ == "__main__": 80 | 81 | sim = Simulator(dut) 82 | sim.add_clock(1/int(100e6)) 83 | sim.add_testbench(convergentRoundBench) 84 | with sim.write_vcd("convergentRound.vcd"): 85 | sim.run() 86 | 87 | # if (True): # export 88 | # top = convergentRound(32, 16) 89 | # with open("controller-firmware/src/amaranth sources/convergentRound.v", "w") as f: 90 | # f.write(verilog.convert(top, name="convergentRound", ports=top.ports)) -------------------------------------------------------------------------------- /controller-firmware/python/src/global_timer.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.lib.wiring import Component, In, Out 3 | from amaranth.sim import Simulator 4 | from registers2 import * 5 | 6 | 7 | 8 | class Global_Timers(Component): 9 | # Global timers for the controller 10 | 11 | 12 | def __init__(self): 13 | 14 | self.number_of_timers = 8 15 | 16 | super().__init__({ 17 | "timer_pulse" : Out(8), 18 | 19 | "trigger": In(1), 20 | 21 | "bram_address": Out(16), 22 | "bram_write_data": Out(32), 23 | "bram_read_data": In(32), 24 | "bram_write_enable": Out(1) 25 | }) 26 | 27 | # setup registers for DMA access 28 | driver_settings = { 29 | "clock_frequency": 25e6 30 | } 31 | self.rm = RegisterMapGenerator("global_timers", ["global_timers"], driver_settings, "Global timers for triggering events in the fpga") 32 | 33 | self.rm.add(Register("counter", rw="w", type="unsigned", width=32, desc="Value that the timer counts down from before triggering", bank_size=self.number_of_timers)) 34 | self.rm.generate() 35 | 36 | def elaborate(self, platform): 37 | m = Module() 38 | 39 | self.current_counts = Array(Signal(name=f"current_count_{_}", shape=16) for _ in range(self.number_of_timers)) 40 | self.reset_counts = Array(Signal(name=f"reset_count_{_}", shape=16, reset=10) for _ in range(self.number_of_timers)) 41 | 42 | # with m.Switch(self.bram_address): 43 | # for i in range(self.number_of_timers): 44 | # with m.Case(i): 45 | # with m.If(self.bram_write_enable): 46 | # m.d.sync_100 += self.reset_counts[i].eq(self.bram_write_data) 47 | 48 | with m.If(self.trigger): # trigger input resets first timer 49 | m.d.sync_25 += self.current_counts[0].eq(self.reset_counts[0]) 50 | 51 | for i in range(self.number_of_timers): 52 | 53 | with m.Elif(self.current_counts[i] != 0): 54 | m.d.sync_25 += self.current_counts[i].eq(self.current_counts[i] - 1) 55 | 56 | with m.If(self.current_counts[i] == 1): # trigger output when timer reaches 1, will create a pulse 1 clock cycle long 57 | m.d.comb += self.timer_pulse[i].eq(1) 58 | if i < self.number_of_timers - 1: 59 | m.d.sync_25 += self.current_counts[i+1].eq(self.reset_counts[i+1]) # start the next timer 60 | 61 | return m 62 | 63 | 64 | 65 | dut = Global_Timers() 66 | async def bench(ctx): 67 | # ctx.set(dut.bram_address, 0) 68 | # ctx.set(dut.bram_write_data, 20) 69 | # ctx.set(dut.bram_write_enable, 1) 70 | # await ctx.tick("sync_100") 71 | # ctx.set(dut.bram_write_enable, 0) 72 | 73 | 74 | await ctx.tick("sync_25").repeat(10) 75 | ctx.set(dut.trigger, 1) 76 | await ctx.tick("sync_25") 77 | ctx.set(dut.trigger, 0) 78 | 79 | await ctx.tick("sync_25").repeat(100) 80 | 81 | 82 | 83 | if __name__ == "__main__": 84 | 85 | sim = Simulator(dut) 86 | sim.add_clock(1/25e6, domain="sync_25") 87 | #sim.add_clock(1/100e6, domain="sync_100") 88 | sim.add_testbench(bench) 89 | with sim.write_vcd("global_timers_test.vcd"): 90 | sim.run() 91 | -------------------------------------------------------------------------------- /controller-firmware/python/src/gpio_node.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.sim import Simulator 3 | from amaranth.back import verilog 4 | from amaranth.lib import wiring 5 | from amaranth.lib.wiring import In, Out 6 | from shift_dma import shift_dma_node 7 | from registers import * 8 | 9 | 10 | class gpio_node(wiring.Component): 11 | 12 | def __init__(self) -> None: 13 | super().__init__({ 14 | "gpio_out": Out(32), 15 | "gpio_out_enable": Out(32), 16 | "gpio_in": In(32), 17 | }) 18 | 19 | self.desc = RTL_Block("gpio_node") 20 | self.desc.addCompatibleDriver("gpio_node") 21 | self.address = 255 22 | 23 | self.desc.addRegister(Register("gpio_out", RegisterDataType.UNSIGNED, ReadWritePermissions.WRITE, "GPIO out register", width=32)) 24 | self.desc.addRegister(Register("gpio_out_enable", RegisterDataType.UNSIGNED, ReadWritePermissions.WRITE, "GPIO out enable register", width=32)) 25 | self.desc.addRegister(Register("gpio_in", RegisterDataType.UNSIGNED, ReadWritePermissions.READ, "GPIO in register", width=32)) 26 | 27 | self.desc.generateAddressMap() 28 | 29 | def elaborate(self, platform): 30 | m = Module() 31 | 32 | m.submodules.shift_dma = self.shift_dma = shift_dma_node(self.address) 33 | 34 | with m.If(self.shift_dma.bram_address == self.desc.getRegistor("gpio_out").address): 35 | m.d.sync += self.shift_dma.bram_read_data.eq(self.gpio_out) 36 | with m.If(self.shift_dma.bram_write_enable == 1): 37 | m.d.sync += self.gpio_out.eq(self.shift_dma.bram_write_data) 38 | 39 | with m.If(self.shift_dma.bram_address == self.desc.getRegistor("gpio_out_enable").address): 40 | m.d.sync += self.shift_dma.bram_read_data.eq(self.gpio_out_enable) 41 | with m.If(self.shift_dma.bram_write_enable == 1): 42 | m.d.sync += self.gpio_out_enable.eq(self.shift_dma.bram_write_data) 43 | 44 | with m.If(self.shift_dma.bram_address == self.desc.getRegistor("gpio_in").address): 45 | m.d.sync += self.shift_dma.bram_read_data.eq(self.gpio_in) 46 | 47 | return m 48 | -------------------------------------------------------------------------------- /controller-firmware/python/src/main.py: -------------------------------------------------------------------------------- 1 | import os, json 2 | 3 | 4 | 5 | 6 | # create required files for a given build config 7 | 8 | def generate_config(build_config_path:str): 9 | # make sure config file exists 10 | if not os.path.exists(build_config_path): 11 | print("Config file not found") 12 | return 13 | -------------------------------------------------------------------------------- /controller-firmware/python/src/sandbox/biquad test.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | encoderCountReal = 0.0 5 | encoderCountInteger = 0 6 | 7 | ENCODER_COUNT = 2**32 8 | 9 | filterData = [0, 0] 10 | 11 | PRESCALER = 1 * 2**32 12 | INPUT_ROUNDING = 32 13 | OUTPUT_ROUNDING = 32 14 | A1 = int(-0.04515176221313341 * 2**32) 15 | A2 = int(-0.9404614587008322 * 2**32) 16 | 17 | B0 = int(0.003596694771508549 * 2**32) 18 | B1 = int(0.007193389543017098 * 2**32) 19 | B2 = int(0.003596694771508549 * 2**32) 20 | 21 | times = [] 22 | filteredPos = [] 23 | realEncoder = [] 24 | integerEncoder = [] 25 | 26 | for i in range(4000): 27 | 28 | # update encoder 29 | encoderCountReal += .001 30 | encoderCountInteger = round(encoderCountReal, 0) 31 | if(encoderCountInteger > ENCODER_COUNT-1): 32 | encoderCountInteger -= ENCODER_COUNT 33 | encoderCountReal -= ENCODER_COUNT 34 | elif(encoderCountInteger < 0): 35 | encoderCountInteger += ENCODER_COUNT 36 | encoderCountReal += ENCODER_COUNT 37 | 38 | if(encoderCountInteger > 0): 39 | print("true") 40 | 41 | if(i == 200): 42 | encoderCountReal += ENCODER_COUNT -50 43 | 44 | temp = int(encoderCountInteger * PRESCALER) 45 | temp -= int(filterData[0] * A1) 46 | temp -= int(filterData[1] * A2) 47 | 48 | dn = round(temp / pow(2, INPUT_ROUNDING), 0) 49 | 50 | 51 | temp2 = int(dn * B0) 52 | temp2 += int(filterData[0] * B1) 53 | temp2 += int(filterData[1] * B2) 54 | 55 | 56 | yn = round(temp2 / pow(2, OUTPUT_ROUNDING), 0) 57 | 58 | filterData.insert(0, dn) 59 | filterData.pop(-1) 60 | 61 | print(filterData) 62 | 63 | times.append(i) 64 | filteredPos.append(yn) 65 | realEncoder.append(encoderCountReal) 66 | integerEncoder.append(encoderCountInteger) 67 | 68 | 69 | plt.plot(times, filteredPos) 70 | plt.plot(times, realEncoder) 71 | plt.plot(times, integerEncoder) 72 | plt.show() 73 | 74 | 75 | # import numpy as np 76 | 77 | # class BiquadFilter: 78 | # def __init__(self, b0, b1, b2, a1, a2): 79 | # # Initialize coefficients 80 | # self.b0, self.b1, self.b2 = b0, b1, b2 81 | # self.a1, self.a2 = a1, a2 82 | 83 | # # Initialize state variables 84 | # self.x1, self.x2 = 0.0, 0.0 85 | # self.y1, self.y2 = 0.0, 0.0 86 | 87 | # def process(self, x): 88 | # # Direct Form II filter implementation 89 | # y = self.b0 * x + self.b1 * self.x1 + self.b2 * self.x2 - self.a1 * self.y1 - self.a2 * self.y2 90 | 91 | # # Update state 92 | # self.x2 = self.x1 93 | # self.x1 = x 94 | # self.y2 = self.y1 95 | # self.y1 = y 96 | 97 | # return y 98 | 99 | # # Example usage 100 | # if __name__ == "__main__": 101 | # # Define filter coefficients (example values) 102 | # b0, b1, b2 = 5.074331248486237e-9, 1.0148662496972475e-8, 5.074331248486237e-9 103 | # a1, a2 = -1.999798506779029, 0.9997985270763541 104 | 105 | # # Create a BiquadFilter object 106 | # filter = BiquadFilter(b0, b1, b2, a1, a2) 107 | 108 | # # Example input signal (simple sinusoidal signal) 109 | # fs = 44100 # Sampling frequency 110 | # t = np.linspace(0, 1, fs, endpoint=False) # Time vector 111 | # input_signal = np.sin(2 * np.pi * 50 * t) # 5 Hz sine wave 112 | 113 | # # Process the signal through the filter 114 | # output_signal = np.array([filter.process(x) for x in input_signal]) 115 | 116 | # # You can use matplotlib to visualize the input and output signals 117 | # import matplotlib.pyplot as plt 118 | # plt.figure() 119 | # plt.subplot(2, 1, 1) 120 | # plt.title('Input Signal') 121 | # plt.plot(t, input_signal) 122 | # plt.subplot(2, 1, 2) 123 | # plt.title('Output Signal') 124 | # plt.plot(t, output_signal) 125 | # plt.show() 126 | 127 | -------------------------------------------------------------------------------- /controller-firmware/python/src/sandbox/current_control.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | # Simulation parameters 5 | R = .1 # Resistance in ohms 6 | L = .001 # Inductance in henries 7 | Vdc = 350.0 # DC supply voltage 8 | f_pwm = 25e3 # PWM frequency in Hz 9 | 10 | current_setpioint = 50.0 # Setpoint for current controller 11 | bemf = 40.0 # Back EMF voltage 12 | 13 | t_stop = 2e-3 # Simulate for 2 ms 14 | dt = .1e-6 # Time step of .1 microsecond 15 | 16 | 17 | 18 | MAX_DUTY_CYCLE = 0.95 19 | MIN_DUTY_CYCLE = 0.00 20 | 21 | 22 | 23 | # Time array 24 | time = np.arange(0, t_stop, dt) 25 | 26 | # Storage for current 27 | current = np.zeros_like(time) 28 | 29 | I = 0.0 # initial current 30 | # Derived parameters 31 | T_pwm = 1.0 / f_pwm 32 | 33 | old_t_mod = 0 34 | 35 | duty_cycle = 0 36 | for i in range(1, len(time)): 37 | 38 | t = time[i] 39 | # Determine if we are in the "on" or "off" portion of the PWM cycle 40 | # Use modulo operation to find where we are within a PWM period 41 | t_mod = t % T_pwm 42 | 43 | if t_mod < old_t_mod: 44 | 45 | applied_voltage = Vdc - bemf 46 | 47 | a = (applied_voltage/R) - current_setpioint 48 | b = (applied_voltage/R) - I 49 | 50 | total_time = -(L/R) * np.log(a/b) 51 | 52 | duty_cycle = min(MAX_DUTY_CYCLE, max(MIN_DUTY_CYCLE, total_time/T_pwm)) 53 | 54 | pass 55 | old_t_mod = t_mod 56 | 57 | on_time = duty_cycle * T_pwm 58 | off_time = T_pwm - on_time 59 | 60 | 61 | if t_mod < on_time: 62 | V = Vdc - bemf 63 | else: 64 | V = -bemf 65 | 66 | # Compute dI/dt 67 | dIdt = (V - R*I) / L 68 | dIdt *= 1 69 | # Integrate using Euler method 70 | I = I + dIdt * dt 71 | 72 | # Store current 73 | current[i] = I 74 | 75 | # Plotting 76 | plt.figure(figsize=(10, 5)) 77 | plt.plot(time*1e4, current, label='Current through inductor') 78 | plt.title('PWM driven RL load') 79 | plt.xlabel('Time (ms)') 80 | plt.ylabel('Current (A)') 81 | plt.grid(True) 82 | plt.legend() 83 | plt.show() 84 | -------------------------------------------------------------------------------- /controller-firmware/python/src/sandbox/fanuc_encoder_sim.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import os 3 | 4 | class rs422_sim(): 5 | def __init__(self, data_file, clock_frequency): 6 | self.file = data_file 7 | 8 | self.current_transfer = 0 9 | self.current_edge = 0 10 | 11 | self.transfers = [] 12 | self.clock_period = 1/clock_frequency 13 | 14 | self.time = 0 15 | self.tx_level = 1 16 | self.req_signal = 0 17 | self.error = False 18 | 19 | 20 | inactive_threshold = 30e-6 # 30us, used to detect when a transfer is not active 21 | 22 | self.req_width = 8e-6 # 8us, width of the request signal 23 | self.req_tolerance = .5e-6 # .5us, tolerance for the request signal (how much the request signal can be off from the expected width) 24 | self.req_response_time = 1e-6 # 1us, time it takes for the transfer to start after the request signal is received 25 | 26 | self.measured_req_width = 0 27 | 28 | with open(self.file, 'r') as f: 29 | reader = csv.reader(f) 30 | temp = [] 31 | for index, row in enumerate(reader): 32 | if index == 0: 33 | continue # skip the header row 34 | 35 | temp.append([float(row[0]), bool(int(row[1]))]) 36 | 37 | if len(temp) > 2 and temp[-1][0] - temp[-2][0] > inactive_threshold: 38 | self.transfers.append(temp[1:-1]) 39 | temp = temp[-2:] 40 | 41 | 42 | def tick(self): 43 | # increment the time by the clock period and update internal state 44 | 45 | if self.req_signal: 46 | self.measured_req_width += self.clock_period 47 | elif self.measured_req_width > 0: 48 | if abs(self.measured_req_width - self.req_width) < self.req_tolerance: 49 | self.start_response() 50 | else: 51 | print("Request signal width not within tolerance") 52 | 53 | self.measured_req_width = 0 54 | 55 | 56 | 57 | self.time += self.clock_period 58 | 59 | def set_request_level(self, level): 60 | self.req_signal = (level != 0) 61 | 62 | def get_tx_level(self): 63 | return self.tx_level 64 | 65 | def inject_error(self): # inject an error into the transfer, this skips an edge somewhere in the transfer 66 | self.error = True 67 | 68 | def start_response(self): 69 | self.current_transfer += 1 70 | self.current_edge = 0 71 | 72 | if self.current_transfer >= len(self.transfers): # loop back to the beginning if we reach the end 73 | self.current_transfer = 0 74 | 75 | self.time = self.transfers[self.current_transfer][0][0] - self.req_response_time 76 | 77 | self.tx_level = 1 78 | 79 | def get_tx_level(self): 80 | if self.current_edge >= len(self.transfers[self.current_transfer]): # nothing to do once we reach the end of the transfer 81 | self.tx_level = 1 # return to idle state, even though the transfer should have set it to zero already 82 | 83 | elif self.time >= self.transfers[self.current_transfer][self.current_edge][0]: 84 | self.tx_level = self.transfers[self.current_transfer][self.current_edge][1] 85 | self.current_edge += 1 86 | 87 | if self.error and (5 < self.current_edge < len(self.transfers[self.current_transfer]) - 5): # flip an edge in the transfer 88 | self.error = False 89 | self.tx_level = not self.tx_level 90 | 91 | return self.tx_level 92 | 93 | 94 | if __name__ == "__main__": 95 | print("Current Directory:", os.getcwd()) 96 | sim = rs422_sim("controller-firmware/python/src/sandbox/fanuc_encoder_rs422.csv", 25e6) 97 | print(sim.transfers) -------------------------------------------------------------------------------- /controller-firmware/python/src/sandbox/instructions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | END = 0 4 | NOP = 1 5 | COPY = 2 6 | 7 | 8 | def create_instruction(source_node, destination_node, source_address, destination_address, instruction): 9 | data = source_node | (destination_node << 8) | (source_address << 16) | (destination_address << 32) | (instruction << 48) 10 | return data 11 | 12 | 13 | def extract_instruction(data): 14 | source_node = data & 0xff 15 | destination_node = (data >> 8) & 0xff 16 | source_address = (data >> 16) & 0xffff 17 | destination_address = (data >> 32) & 0xffff 18 | instruction = (data >> 48) & 0xf 19 | 20 | print('source_node:', source_node) 21 | print('destination_node:', destination_node) 22 | print('source_address:', source_address) 23 | print('destination_address:', destination_address) 24 | print('instruction:', instruction) 25 | 26 | return source_node, destination_node, source_address, destination_address, instruction 27 | 28 | # extract_instruction(562950020530432) 29 | # print("\n\n") 30 | # extract_instruction(562954315563264) 31 | # print("\n\n") 32 | # extract_instruction(562962905563392) 33 | # print("\n\n") 34 | # extract_instruction(562949953552385) 35 | # print("\n\n") 36 | # extract_instruction(562954248650753) 37 | # print("\n\n") 38 | 39 | extract_instruction(562958543355906) 40 | print("\n\n") 41 | 42 | extract_instruction(562962838388738) 43 | print("\n\n") 44 | 45 | extract_instruction(562967133421570) 46 | print("\n\n") 47 | 48 | extract_instruction(562971428454402) 49 | print("\n\n") 50 | 51 | extract_instruction(562950020530432) 52 | print("\n\n") 53 | 54 | print(np.log2(256)) 55 | 56 | 57 | #0xb6fc8008 58 | #0xb6fc800c 59 | -------------------------------------------------------------------------------- /controller-firmware/python/src/sandbox/test1.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | for i in range(32): 9 | print(f"4 bit value: {i%16}\tstep: {1}\t8 bit value: {i%16 << 4}\tstep: {1<<4}") -------------------------------------------------------------------------------- /controller-firmware/python/src/sin_cos_lookup.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.sim import Simulator 3 | from amaranth.back import verilog 4 | from amaranth.lib.memory import Memory 5 | from src.convergent_round import convergentRound 6 | import matplotlib.pyplot as plt 7 | import numpy as np 8 | 9 | 10 | class sin_cos_lookup_32(Elaboratable): 11 | """ 12 | high precision 32bit sin/cos lookup, commonly used for convering wrapping signals such as encoders to a continuous signal for processing 13 | """ 14 | 15 | def __init__(self, tableSizePower = 8) -> None: 16 | 17 | """ 18 | tableSize: number of lookup values to store in memory, intermediate values are found through linear interpolation. must be a power of 2 19 | """ 20 | 21 | self.tableSize = 2**tableSizePower 22 | self.tableSizePower = tableSizePower 23 | 24 | # ports 25 | self.dataIn = Signal(32) 26 | self.sinOut = Signal(shape=signed(32)) 27 | self.cosOut = Signal(shape=signed(32)) 28 | 29 | self.outputReady = Signal() 30 | 31 | self.ports = [ 32 | self.outputReady, 33 | self.dataIn, 34 | self.sinOut, 35 | self.cosOut, 36 | ] 37 | # internal signals 38 | self.oldDataIn = Signal(32) 39 | self.sinSign = Signal(shape=signed(2)) 40 | self.cosSign = Signal(shape=signed(2)) 41 | 42 | self.state = Signal(6) 43 | 44 | 45 | def elaborate(self, platform): 46 | m = Module() 47 | 48 | self.sinTable = [] 49 | for i in range(self.tableSize+1): # fill table with sin/cos values 50 | self.sinTable.append( int(np.ceil(np.sin(np.pi/2 * (i/(self.tableSize))) * 2**31-1)) | int(np.ceil(np.cos(np.pi/2 * (i/(self.tableSize))) * 2**31-1)) << 32) 51 | 52 | self.sinTable[0] = (2**31-1)<<32 53 | 54 | m.submodules.sinTable = self.sinTableMemory = Memory(shape=signed(64), depth=self.tableSize+1, init=self.sinTable) 55 | 56 | self.readPort = self.sinTableMemory.read_port() 57 | self.readPort2 = self.sinTableMemory.read_port() 58 | 59 | with m.If((self.dataIn[-1]==0) & (self.dataIn[-2]==0)): # 0-25% 60 | m.d.comb += self.readPort.addr.eq(self.dataIn.bit_select(32 - 2 - self.tableSizePower, self.tableSizePower)) 61 | m.d.comb += self.readPort2.addr.eq(self.dataIn.bit_select(32 - 2 - self.tableSizePower, self.tableSizePower) + 1) 62 | m.d.comb += self.sinSign.eq(1) 63 | m.d.comb += self.cosSign.eq(1) 64 | 65 | with m.Elif((self.dataIn[-1]==0) & (self.dataIn[-2]==1)): # 25-50% 66 | m.d.comb += self.readPort.addr.eq(self.tableSize - self.dataIn.bit_select(32 - 2 - self.tableSizePower, self.tableSizePower)) 67 | m.d.comb += self.readPort2.addr.eq(self.tableSize - self.dataIn.bit_select(32 - 2 - self.tableSizePower, self.tableSizePower)-1) 68 | m.d.comb += self.sinSign.eq(1) 69 | m.d.comb += self.cosSign.eq(-1) 70 | 71 | with m.Elif((self.dataIn[-1]==1) & (self.dataIn[-2]==0)): # 50-75% 72 | m.d.comb += self.readPort.addr.eq(self.dataIn.bit_select(32 - 2 - self.tableSizePower, self.tableSizePower)) 73 | m.d.comb += self.readPort2.addr.eq(self.dataIn.bit_select(32 - 2 - self.tableSizePower, self.tableSizePower) + 1) 74 | m.d.comb += self.sinSign.eq(-1) 75 | m.d.comb += self.cosSign.eq(-1) 76 | 77 | with m.Elif((self.dataIn[-1]==1) & (self.dataIn[-2]==1)): # 75-100% 78 | m.d.comb += self.readPort.addr.eq(self.tableSize - self.dataIn.bit_select(32 - 2 - self.tableSizePower, self.tableSizePower)) 79 | m.d.comb += self.readPort2.addr.eq(self.tableSize - self.dataIn.bit_select(32 - 2 - self.tableSizePower, self.tableSizePower)-1) 80 | m.d.comb += self.sinSign.eq(-1) 81 | m.d.comb += self.cosSign.eq(1) 82 | 83 | m.d.sync += self.sinOut.eq(((self.readPort.data.bit_select(0, 32)*(~self.dataIn.bit_select(0, 32-1-self.tableSizePower-1)) + self.readPort2.data.bit_select(0, 32)*(self.dataIn.bit_select(0, 32-1-self.tableSizePower-1))).shift_right(32-1-self.tableSizePower-1)) * self.sinSign) 84 | 85 | m.d.sync += self.cosOut.eq(((self.readPort.data.bit_select(32, 32)*(~self.dataIn.bit_select(0, 32-1-self.tableSizePower-1)) + self.readPort2.data.bit_select(32, 32)*(self.dataIn.bit_select(0, 32-1-self.tableSizePower-1))).shift_right(32-1-self.tableSizePower-1)) * self.cosSign) 86 | 87 | return m 88 | 89 | clock = int(100e6) 90 | dut = sin_cos_lookup_32(10) 91 | 92 | ENCODER_COUNT = 2**16 93 | 94 | times = [] 95 | sin = [] 96 | cos = [] 97 | inputs = [] 98 | idealSin = [] 99 | idealCos = [] 100 | sinError = [] 101 | cosError = [] 102 | 103 | 104 | async def sincosBench(ctx): 105 | for i in range(0, 2**32, 2**20): 106 | 107 | idealSin.append(np.sin(np.pi*2 * i/(2**32-1)) * 2**31-1) 108 | idealCos.append(np.cos(np.pi*2 * i/(2**32-1)) * 2**31-1) 109 | 110 | inputVal = i 111 | ctx.set(dut.dataIn, inputVal) 112 | await ctx.tick() 113 | 114 | x=1 115 | while(not ctx.get(dut.outputReady) and x > 0): 116 | x -= 1 117 | await ctx.tick() 118 | 119 | times.append(i) 120 | sin.append(ctx.get(dut.sinOut)) 121 | cos.append(ctx.get(dut.cosOut)) 122 | sinError.append(sin[-1] - idealSin[-1]) 123 | cosError.append(cos[-1] - idealCos[-1]) 124 | inputs.append(inputVal) 125 | 126 | 127 | 128 | if __name__ == "__main__": 129 | 130 | sim = Simulator(dut) 131 | sim.add_clock(1/clock) 132 | sim.add_testbench(sincosBench) 133 | with sim.write_vcd("sin_cos.vcd"): 134 | sim.run() 135 | 136 | # plt.plot(times, sin) 137 | # plt.plot(times, cos) 138 | # plt.plot(times, inputs) 139 | # plt.plot(times, idealSin) 140 | # plt.plot(times, idealCos) 141 | plt.plot(times, sinError) 142 | #plt.plot(times, cosError) 143 | plt.show() 144 | 145 | # if (True): # export 146 | # top = biquad_32(int(100e6)) 147 | # with open("controller-firmware/src/amaranth sources/biquad_32.v", "w") as f: 148 | # f.write(verilog.convert(top, name="biquad_32", ports=top.ports)) -------------------------------------------------------------------------------- /controller-firmware/python/src/testing_block.py: -------------------------------------------------------------------------------- 1 | from amaranth import * 2 | from amaranth.sim import Simulator 3 | from amaranth.lib.memory import Memory 4 | from amaranth.back import verilog 5 | from amaranth.lib.wiring import In, Out 6 | from amaranth.lib import wiring 7 | 8 | 9 | # minimal rtl block for testing, just some bram 10 | 11 | 12 | class test_block(wiring.Component): 13 | 14 | def __init__(self, size) -> None: 15 | super().__init__({ 16 | "write_data": In(32), 17 | "write_enable": In(1), 18 | "address": In(16), 19 | "read_data": Out(32) 20 | }) 21 | self.size = size 22 | 23 | def elaborate(self, platform): 24 | m = Module() 25 | 26 | m.submodules.memory = self.memory = Memory(shape=unsigned(32), depth=self.size, init=[]) # small memory for testing 27 | 28 | self.externalReadPort = self.memory.read_port(domain="sync_100") 29 | self.externalWritePort = self.memory.write_port(domain="sync_100") 30 | 31 | # connect external memory interfaces 32 | m.d.comb += self.externalReadPort.addr.eq(self.address) 33 | m.d.comb += self.externalWritePort.addr.eq(self.address) 34 | m.d.comb += self.externalWritePort.data.eq(self.write_data) 35 | m.d.comb += self.externalWritePort.en.eq(self.write_enable) 36 | m.d.comb += self.read_data.eq(self.externalReadPort.data) 37 | 38 | return m 39 | -------------------------------------------------------------------------------- /controller-software/config/electrical/motors/motor_linear.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | class LINEAR_MOTOR: 4 | def __init__(self): 5 | self.types = [self.PMAC] 6 | self.motor_type = None 7 | self.user_params = None 8 | 9 | 10 | @dataclass 11 | class COMMON: 12 | # Common motor parameters 13 | rated_speed: float = field(default=None, metadata={'unit': 'm/s', 'desc': 'Rated speed of the motor', 'required':True}) 14 | max_speed: float = field(default=None, metadata={'unit': 'm/s', 'desc': 'Maximum speed of the motor', 'required':False}) 15 | rated_voltage: float = field(default=None, metadata={'unit': 'V', 'desc': 'Rated voltage of the motor', 'required':True}) 16 | rated_current: float = field(default=None, metadata={'unit': 'A', 'desc': 'Rated current of the motor', 'required':True}) 17 | soft_max_current: float = field(default=None, metadata={'unit': 'A', 'desc': 'Soft maximum current of the motor', 'required':False}) 18 | hard_max_current: float = field(default=None, metadata={'unit': 'A', 'desc': 'Hard maximum current of the motor, beyond which the motor will be damaged', 'required':True}) 19 | force_constant: float = field(default=None, metadata={'unit': 'N/A', 'desc': 'Force constant of the motor', 'required':False}) 20 | bemf_constant: float = field(default=None, metadata={'unit': 'V/(m/s)', 'desc': 'Back EMF constant of the motor', 'required':False}) 21 | phase_resistance: float = field(default=None, metadata={'unit': 'Ohm', 'desc': 'Phase resistance of the motor', 'required':False}) 22 | phase_inductance: float = field(default=None, metadata={'unit': 'H', 'desc': 'Phase inductance of the motor', 'required':False}) 23 | mass: float = field(default=None, metadata={'unit': 'kg', 'desc': 'Mass of the moving part of the motor', 'required':False}) 24 | thermal_time_constant: float = field(default=None, metadata={'unit': 's', 'desc': 'Thermal time constant of the motor', 'required':False}) 25 | thermal_resistance: float = field(default=None, metadata={'unit': 'K/W', 'desc': 'Thermal resistance of the motor to ambient', 'required':False}) 26 | max_temperature: float = field(default=None, metadata={'unit': 'C', 'desc': 'Maximum temperature of the motor', 'required':False}) 27 | 28 | @dataclass 29 | class PMAC: 30 | # PMAC specific parameters 31 | pole_distance: int = field(default=None, metadata={'desc': 'Distance between poles on the motor', 'required':False}) 32 | -------------------------------------------------------------------------------- /controller-software/config/electrical/motors/motor_rotary.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from enum import Enum, auto 3 | import sys 4 | import os 5 | 6 | motor_drives_dir = os.path.dirname(os.path.realpath(__file__)) 7 | electrical_dir = os.path.dirname(motor_drives_dir) 8 | 9 | sys.path.append(electrical_dir) 10 | 11 | from parameters import TEXT_PARAMETER, FLOAT_PARAMETER, INT_PARAMETER, set_parents 12 | from connectors import CONNECTOR, CONNECTOR_TYPES 13 | from user_params import USER_PARAMS 14 | 15 | 16 | @dataclass 17 | class CONNECTORS: 18 | # Motor connectors 19 | motor: None = field(default=None, metadata={'desc': 'What is connected to the motor', 'required':True, 'chainable':False}) 20 | 21 | @dataclass 22 | class COMMON: 23 | # Common motor parameters 24 | rated_speed: FLOAT_PARAMETER = field(default_factory=lambda: FLOAT_PARAMETER(description="Rated speed of the motor", unit="RPM", required=True)) 25 | rated_voltage: FLOAT_PARAMETER = field(default_factory=lambda: FLOAT_PARAMETER(description="Rated voltage of the motor", unit="Volts", required=True)) 26 | hard_max_current: FLOAT_PARAMETER = field(default_factory=lambda: FLOAT_PARAMETER(description="Hard maximum current of the motor, beyond which the motor will be demagnetized", unit="Amps", required=True)) 27 | 28 | # rated_speed: float = field(default=None, metadata={'unit': 'RPM', 'desc': 'Rated speed of the motor', 'required':True}) 29 | # max_speed: float = field(default=None, metadata={'unit': 'RPM', 'desc': 'Maximum speed of the motor', 'required':False}) 30 | # rated_voltage: float = field(default=None, metadata={'unit': 'V', 'desc': 'Rated voltage of the motor', 'required':True}) 31 | # rated_current: float = field(default=None, metadata={'unit': 'A', 'desc': 'Rated current of the motor', 'required':True}) 32 | # soft_max_current: float = field(default=None, metadata={'unit': 'A', 'desc': 'Soft maximum current of the motor', 'required':False}) 33 | # hard_max_current: float = field(default=None, metadata={'unit': 'A', 'desc': 'Hard maximum current of the motor, beyond which the motor will be demagnetized', 'required':True}) 34 | # torque_constant: float = field(default=None, metadata={'unit': 'Nm/A', 'desc': 'Torque constant of the motor', 'required':False}) 35 | # bemf_constant: float = field(default=None, metadata={'unit': 'V/(rad/s)', 'desc': 'Back EMF constant of the motor', 'required':False}) 36 | # phase_resistance: float = field(default=None, metadata={'unit': 'Ohm', 'desc': 'Phase resistance of the motor', 'required':False}) 37 | # phase_inductance: float = field(default=None, metadata={'unit': 'H', 'desc': 'Phase inductance of the motor', 'required':False}) 38 | # inertia: float = field(default=None, metadata={'unit': 'kg*m^2', 'desc': 'Inertia of the motor', 'required':False}) 39 | # thermal_time_constant: float = field(default=None, metadata={'unit': 's', 'desc': 'Thermal time constant of the motor', 'required':False}) 40 | # thermal_resistance: float = field(default=None, metadata={'unit': 'K/W', 'desc': 'Thermal resistance of the motor to ambient', 'required':False}) 41 | # max_temperature: float = field(default=None, metadata={'unit': 'C', 'desc': 'Maximum temperature of the motor', 'required':False}) 42 | 43 | def __post_init__(self): 44 | set_parents(self) 45 | 46 | @dataclass 47 | class PMSM: 48 | # PMSM specific parameters 49 | pole_pairs = INT_PARAMETER(description="Number of pole pairs in the motor", required=False) 50 | # pole_pairs: int = field(default=None, metadata={'desc': 'Number of pole pairs in the motor', 'required':False}) 51 | 52 | def __post_init__(self): 53 | set_parents(self) 54 | 55 | @dataclass 56 | class DC: 57 | # DC specific parameters 58 | pass 59 | 60 | def __post_init__(self): 61 | set_parents(self) 62 | 63 | # @dataclass 64 | # class STEPPER_BIPOLAR: 65 | # # Bipolar stepper specific parameters 66 | # steps_per_rev: int = field(default=None, metadata={'desc': 'Number of steps per revolution of the motor', 'required':True}) 67 | 68 | # @dataclass 69 | # class STEPPER_UNIPOLAR: 70 | # # Unipolar stepper specific parameters 71 | # steps_per_rev: int = field(default=None, metadata={'desc': 'Number of steps per revolution of the motor', 'required':True}) 72 | 73 | # @dataclass 74 | # class INDUCTION: 75 | # # Induction motor specific parameters 76 | # #TODO: Add induction motor specific parameters 77 | # pass 78 | 79 | 80 | 81 | 82 | class ROTARY_MOTOR: 83 | def __init__(self): 84 | self.module_type = 'motor' 85 | self.common_params = COMMON() 86 | self.user_params = USER_PARAMS() 87 | self.mode_params = { 88 | self.MODES.PMSM: PMSM(), 89 | self.MODES.DC: DC() 90 | } 91 | self.mode = None 92 | self.connectors = {} 93 | 94 | self.connectors["motor"] = CONNECTOR(description="Motor terminals", required=True, exclusive=True, enabled=True, direction="input") 95 | 96 | # Automatically set the parent reference in child instances 97 | for connector in self.connectors.values(): 98 | connector.parent = self 99 | 100 | class MODES(Enum): 101 | PMSM = auto() 102 | DC = auto() 103 | 104 | def common_parameters(self): 105 | return self.common_params 106 | 107 | def mode_parameters(self): 108 | return self.mode_params[self.mode] 109 | 110 | def set_mode(self, mode: MODES): 111 | if mode == self.MODES.PMSM: 112 | self.connectors["motor"].can_connect_to=[CONNECTOR_TYPES.DRIVE_PMSM] 113 | self.connectors["motor"].type=CONNECTOR_TYPES.MOTOR_PMSM 114 | self.connectors["motor"].exclusive = True 115 | self.connectors["motor"].hardware_identifier = "UVW" 116 | self.mode = mode 117 | elif mode == self.MODES.DC: 118 | self.connectors["motor"].can_connect_to=[CONNECTOR_TYPES.DRIVE_DC, CONNECTOR_TYPES.POWER_DC] 119 | self.connectors["motor"].type=CONNECTOR_TYPES.MOTOR_DC 120 | self.connectors["motor"].exclusive = False 121 | self.connectors["motor"].hardware_identifier = "+ -" 122 | self.mode = mode 123 | else: 124 | raise ValueError('Motor type not supported') 125 | 126 | self.connectors["motor"].is_connected_to = [] 127 | -------------------------------------------------------------------------------- /controller-software/config/electrical/parameters.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field, asdict 2 | import math 3 | from typing import Any 4 | 5 | def set_parents(parent): 6 | # Automatically set the parent reference in child instances 7 | for field_name in parent.__dataclass_fields__: 8 | value = getattr(parent, field_name) 9 | if hasattr(value, 'parent'): 10 | value.parent = parent 11 | 12 | @dataclass 13 | class TEXT_PARAMETER: 14 | description: str = field(default="", metadata={'desc': 'Description'}) 15 | required: bool = field(default=False, metadata={'desc': 'Required for system to startup (not able to be auto calibrated or calculated)'}) 16 | value: str = field(default=None, metadata={'desc': 'Input text'}) 17 | auto_fill_enable: bool = field(default=False, metadata={'desc': 'Locked from being auto filled'}) 18 | manual_fill_enable: bool = field(default=True, metadata={'desc': 'Locked from being manually filled'}) 19 | max_length: int = field(default=100, metadata={'desc': 'Maximum length of the text'}) 20 | auto_fill_by: list = field(default_factory=list, metadata={'desc': 'External parameters that can auto fill this parameter'}) 21 | 22 | parent: Any = field(default=None, repr=False, compare=False, metadata={'exclude': True}) 23 | 24 | 25 | @dataclass 26 | class FLOAT_PARAMETER: 27 | description: str = field(default="", metadata={'desc': 'Description'}) 28 | unit: str = field(default="", metadata={'desc': 'Unit of the parameter'}) 29 | required: bool = field(default=False, metadata={'desc': 'Required for system to startup (not able to be auto calibrated or calculated)'}) 30 | value: float = field(default=None, metadata={'desc': 'Input value'}) 31 | auto_fill_enable: bool = field(default=False, metadata={'desc': 'Locked from being auto filled'}) 32 | manual_fill_enable: bool = field(default=True, metadata={'desc': 'Locked from being manually filled'}) 33 | max_value: float = field(default=math.inf, metadata={'desc': 'Maximum value'}) 34 | min_value: float = field(default=-math.inf, metadata={'desc': 'Minimum value'}) 35 | auto_fill_by: list = field(default_factory=list, metadata={'desc': 'External parameters that can auto fill this parameter'}) 36 | 37 | parent: Any = field(default=None, repr=False, compare=False, metadata={'exclude': True}) 38 | 39 | @dataclass 40 | class INT_PARAMETER: 41 | description: str = field(default="", metadata={'desc': 'Description'}) 42 | unit: str = field(default="", metadata={'desc': 'Unit of the parameter'}) 43 | required: bool = field(default=False, metadata={'desc': 'Required for system to startup (not able to be auto calibrated or calculated)'}) 44 | value: int = field(default=None, metadata={'desc': 'Input value'}) 45 | auto_fill_enable: bool = field(default=False, metadata={'desc': 'Locked from being auto filled'}) 46 | manual_fill_enable: bool = field(default=True, metadata={'desc': 'Locked from being manually filled'}) 47 | max_value: int = field(default=math.inf, metadata={'desc': 'Maximum value'}) 48 | min_value: int = field(default=-math.inf, metadata={'desc': 'Minimum value'}) 49 | auto_fill_by: list = field(default_factory=list, metadata={'desc': 'External parameters that can auto fill this parameter'}) 50 | 51 | parent: Any = field(default=None, repr=False, compare=False, metadata={'exclude': True}) 52 | 53 | 54 | # User defined parameters that aren't device specific 55 | @dataclass 56 | class USER_PARAMS: 57 | 58 | name: TEXT_PARAMETER = field(default_factory=lambda: TEXT_PARAMETER(description="Name of the device", required=True, max_length=25)) 59 | model_number: TEXT_PARAMETER = field(default_factory=lambda: TEXT_PARAMETER(description="Model number of the device", max_length=100)) 60 | manufacturer: TEXT_PARAMETER = field(default_factory=lambda: TEXT_PARAMETER(description="Manufacturer of the device", max_length=100)) 61 | location: TEXT_PARAMETER = field(default_factory=lambda: TEXT_PARAMETER(description="Location of the device", max_length=100)) 62 | description: TEXT_PARAMETER = field(default_factory=lambda: TEXT_PARAMETER(description="Description of the device", max_length=1000)) 63 | 64 | def __post_init__(self): 65 | set_parents(self) -------------------------------------------------------------------------------- /controller-software/config/electrical/power/line_reactor.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | class LINE_REACTOR: 4 | pass -------------------------------------------------------------------------------- /controller-software/config/electrical/power/power_sources.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | class POWER_SOURCE: 4 | def __init__(self): 5 | self.types = [self.AC1, self.AC3, self.DC, self.BATTERY] 6 | self.supply_type = None 7 | self.user_params = None 8 | 9 | @dataclass 10 | class COMMON: 11 | # common power source parameters 12 | nominal_voltage: float = field(default=None, metadata={'unit': 'V', 'desc': 'Nominal voltage of the power source', 'required':True}) 13 | max_positive_current: float = field(default=None, metadata={'unit': 'A', 'desc': 'Maximum current allowed to be pulled from the supply', 'required':True}) 14 | max_negative_current: float = field(default=0.0, metadata={'unit': 'A', 'desc': 'Maximum current allowed to be pushed to the supply', 'required':True}) 15 | 16 | @dataclass 17 | class AC1: 18 | # AC1 specific parameters 19 | line_frequency: float = field(default=None, metadata={'unit': 'Hz', 'desc': 'Line frequency in Hz', 'required':True}) 20 | allowed_voltage_fluxuation: float = field(default=10, metadata={'unit': '%', 'desc': 'Max voltage fluxuation in percent', 'required':True}) 21 | 22 | @dataclass 23 | class AC3: 24 | # AC3 specific parameters 25 | line_frequency: float = field(default=None, metadata={'unit': 'Hz', 'desc': 'Line frequency in Hz', 'required':True}) 26 | allowed_voltage_fluxuation: float = field(default=10, metadata={'unit': '%', 'desc': 'Max voltage fluxuation in percent', 'required':True}) 27 | 28 | @dataclass 29 | class DC: 30 | # DC specific parameters 31 | allowed_voltage_fluxuation: float = field(default=10, metadata={'unit': '%', 'desc': 'Max voltage fluxuation in percent', 'required':True}) 32 | 33 | @dataclass 34 | class BATTERY: 35 | # Battery specific parameters 36 | max_voltage: float = field(default=None, metadata={'unit': 'V', 'desc': 'Maximum voltage of the battery', 'required':True}) 37 | min_voltage: float = field(default=None, metadata={'unit': 'V', 'desc': 'Minimum voltage of the battery', 'required':True}) 38 | over_under_voltage_trip_time: float = field(default=5, metadata={'unit': 's', 'desc': 'Time before the battery trips on over/under voltage', 'required':True}) 39 | max_temperature: float = field(default=None, metadata={'unit': 'C', 'desc': 'Maximum temperature allowed for the battery', 'required':False}) 40 | min_temperature: float = field(default=None, metadata={'unit': 'C', 'desc': 'Minimum temperature allowed for the battery', 'required':False}) 41 | cell_count: int = field(default=None, metadata={'desc': 'Number of cells in the battery', 'required':False}) 42 | chemistry: str = field(default=None, metadata={'desc': 'Chemistry of the battery', 'required':False}) -------------------------------------------------------------------------------- /controller-software/config/electrical/test.py: -------------------------------------------------------------------------------- 1 | from motor_drives.em_hvsd import EM_HVSD 2 | from motors.motor_rotary import ROTARY_MOTOR 3 | from connectors import CONNECTOR_FUNCTIONS 4 | 5 | if __name__ == '__main__': 6 | motor = ROTARY_MOTOR() 7 | drive = EM_HVSD() 8 | 9 | motor.set_mode(motor.MODES.PMSM) 10 | motor.common_params.rated_speed.value = 3000 11 | motor.common_params.rated_voltage.value = 220 12 | motor.common_params.hard_max_current.value = 10 13 | #motor.specific_params.pole_pairs.value = 4 14 | 15 | drive.set_mode(drive.MODES.PMSM_FOC) 16 | drive.common_params.serial_address.value = 1 17 | 18 | print(CONNECTOR_FUNCTIONS.validate_connect_to(drive.connectors["motor"], [motor.connectors["motor"]])) 19 | 20 | print(CONNECTOR_FUNCTIONS.connect_to(drive.connectors["motor"], [motor.connectors["motor"]])) 21 | 22 | print(CONNECTOR_FUNCTIONS.update_from_connector(drive.connectors["motor"], motor.connectors["motor"])) -------------------------------------------------------------------------------- /controller-software/config/electrical/user_params.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | 3 | 4 | # default param options to add to every device class 5 | 6 | @dataclass 7 | class USER_PARAMS: 8 | 9 | # User defined parameters 10 | name: str = field(default="PMAC Motor", metadata={'desc': 'Name of the motor', 'required':False}) 11 | model_number: str = field(default=None, metadata={'desc': 'Model number of the motor', 'required':False}) 12 | manufacturer: str = field(default=None, metadata={'desc': 'Manufacturer of the motor', 'required':False}) 13 | location: str = field(default=None, metadata={'desc': 'Location of the motor', 'required':False}) 14 | description: str = field(default=None, metadata={'desc': 'Description of the motor', 'required':False}) -------------------------------------------------------------------------------- /controller-software/core/.gitignore: -------------------------------------------------------------------------------- 1 | /.ssh 2 | /build -------------------------------------------------------------------------------- /controller-software/core/.vscode/controller-software-core.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "../" 5 | } 6 | ], 7 | "settings": { 8 | "files.associations": { 9 | "algorithm": "cpp", 10 | "array": "cpp", 11 | "atomic": "cpp", 12 | "bit": "cpp", 13 | "bitset": "cpp", 14 | "cctype": "cpp", 15 | "charconv": "cpp", 16 | "chrono": "cpp", 17 | "cinttypes": "cpp", 18 | "clocale": "cpp", 19 | "cmath": "cpp", 20 | "compare": "cpp", 21 | "complex": "cpp", 22 | "concepts": "cpp", 23 | "condition_variable": "cpp", 24 | "cstddef": "cpp", 25 | "cstdint": "cpp", 26 | "cstdio": "cpp", 27 | "cstdlib": "cpp", 28 | "cstring": "cpp", 29 | "ctime": "cpp", 30 | "cwchar": "cpp", 31 | "cwctype": "cpp", 32 | "exception": "cpp", 33 | "format": "cpp", 34 | "forward_list": "cpp", 35 | "fstream": "cpp", 36 | "functional": "cpp", 37 | "future": "cpp", 38 | "initializer_list": "cpp", 39 | "iomanip": "cpp", 40 | "ios": "cpp", 41 | "iosfwd": "cpp", 42 | "iostream": "cpp", 43 | "istream": "cpp", 44 | "iterator": "cpp", 45 | "limits": "cpp", 46 | "list": "cpp", 47 | "locale": "cpp", 48 | "map": "cpp", 49 | "memory": "cpp", 50 | "mutex": "cpp", 51 | "new": "cpp", 52 | "numeric": "cpp", 53 | "optional": "cpp", 54 | "ostream": "cpp", 55 | "random": "cpp", 56 | "ratio": "cpp", 57 | "set": "cpp", 58 | "sstream": "cpp", 59 | "stdexcept": "cpp", 60 | "stop_token": "cpp", 61 | "streambuf": "cpp", 62 | "string": "cpp", 63 | "system_error": "cpp", 64 | "thread": "cpp", 65 | "tuple": "cpp", 66 | "type_traits": "cpp", 67 | "typeinfo": "cpp", 68 | "unordered_map": "cpp", 69 | "unordered_set": "cpp", 70 | "utility": "cpp", 71 | "variant": "cpp", 72 | "vector": "cpp", 73 | "xfacet": "cpp", 74 | "xhash": "cpp", 75 | "xiosbase": "cpp", 76 | "xlocale": "cpp", 77 | "xlocbuf": "cpp", 78 | "xlocinfo": "cpp", 79 | "xlocmes": "cpp", 80 | "xlocmon": "cpp", 81 | "xlocnum": "cpp", 82 | "xloctime": "cpp", 83 | "xmemory": "cpp", 84 | "xstring": "cpp", 85 | "xtr1common": "cpp", 86 | "xtree": "cpp", 87 | "xutility": "cpp", 88 | "cstdarg": "cpp", 89 | "*.tcc": "cpp", 90 | "deque": "cpp", 91 | "memory_resource": "cpp", 92 | "string_view": "cpp", 93 | "any": "cpp", 94 | "codecvt": "cpp", 95 | "filesystem": "cpp", 96 | "valarray": "cpp", 97 | "hash_map": "cpp", 98 | "hash_set": "cpp" 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /controller-software/core/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Remote Debug on Zynq", 6 | "type": "cppdbg", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/zynq_files/controller/bin/controller_core", 9 | "args": [], 10 | "stopAtEntry": true, 11 | "targetArchitecture": "arm", 12 | "cwd": "${workspaceFolder}/build/ZYNQ-Debug", 13 | "environment": [], 14 | "externalConsole": false, 15 | "MIMode": "gdb", 16 | "miDebuggerPath": "/usr/bin/gdb-multiarch", // Ensure gdb-multiarch is installed in WSL 17 | "setupCommands": [ 18 | { 19 | "description": "Enable pretty-printing for gdb", 20 | "text": "-enable-pretty-printing", 21 | "ignoreFailures": true 22 | } 23 | ], 24 | "preLaunchTask": "Launch gdbserver on Zynq", 25 | "miDebuggerServerAddress": "192.168.1.238:1234", // Replace with your SSH alias and port 26 | "sourceFileMap": { 27 | "/home/em-os/controller/": "${workspaceFolder}/zynq_files/controller/" // Adjust if your source files are in a different location 28 | }, 29 | "logging": { 30 | "trace": true, 31 | "traceResponse": true, 32 | "engineLogging": true 33 | } 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /controller-software/core/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "tasks": [ 3 | // { 4 | // "type": "cppbuild", 5 | // "label": "C/C++: g++ build active file", 6 | // "command": "/usr/bin/g++", 7 | // "args": [ 8 | // "-fdiagnostics-color=always", 9 | // "-g", 10 | // "${file}", 11 | // "-o", 12 | // "${fileDirname}/${fileBasenameNoExtension}", 13 | // "-lrt", 14 | // "-pthread" 15 | // ], 16 | // "options": { 17 | // "cwd": "${fileDirname}" 18 | // }, 19 | // "problemMatcher": [ 20 | // "$gcc" 21 | // ], 22 | // "group": "build", 23 | // "detail": "Task generated by Debugger." 24 | // }, 25 | { 26 | "type": "cmake", 27 | "label": "Build", 28 | "command": "build", 29 | "targets": [ 30 | "all" 31 | ], 32 | "preset": "${command:cmake.activeBuildPresetName}", 33 | "group": { 34 | "kind": "build", 35 | "isDefault": true 36 | }, 37 | "problemMatcher": [], 38 | "detail": "CMake template build task" 39 | }, 40 | { 41 | "label": "Copy Files to Zynq", 42 | "type": "shell", 43 | "command": "scp", 44 | "args": [ 45 | "-rp", 46 | "${workspaceFolder}/zynq_files/*", 47 | "zynq:/home/em-os/" 48 | ], 49 | "dependsOn": "Build", 50 | "group": { 51 | "kind": "build", 52 | "isDefault": false 53 | }, 54 | "problemMatcher": [] 55 | }, 56 | 57 | { 58 | "label": "Stop zynq processes", 59 | "type": "shell", 60 | "command": "ssh", 61 | "args": [ 62 | "zynq", 63 | "sudo -S", 64 | "pkill", 65 | "-f", 66 | "/home/em-os/controller/" 67 | ], 68 | "group": { 69 | "kind": "build", 70 | "isDefault": false 71 | }, 72 | "problemMatcher": [] 73 | }, 74 | 75 | { 76 | "label": "Run Binary on Zynq", 77 | "type": "shell", 78 | "command": "ssh", 79 | "args": [ 80 | "zynq", 81 | "chmod +x /home/em-os/controller/bin/controller_core && sudo -S /home/em-os/controller/bin/controller_core" 82 | ], 83 | "dependsOn": "Copy Files to Zynq", 84 | "group": { 85 | "kind": "test", 86 | "isDefault": true 87 | }, 88 | "presentation": { 89 | "echo": true, 90 | "reveal": "always", 91 | "focus": false, 92 | "panel": "shared" 93 | }, 94 | "problemMatcher": [] 95 | }, 96 | 97 | { 98 | "label": "Launch gdbserver on Zynq", 99 | "type": "shell", 100 | "command": "ssh", 101 | "args": [ 102 | "zynq", 103 | "sudo -S gdbserver :1234 /home/em-os/controller/bin/controller_core" 104 | ], 105 | "dependsOn": "Copy Files to Zynq", 106 | "group": { 107 | "kind": "test", 108 | "isDefault": false 109 | }, 110 | "isBackground": true, 111 | "presentation": { 112 | "echo": true, 113 | "reveal": "always", 114 | "focus": false, 115 | "panel": "dedicated" 116 | }, 117 | "problemMatcher": [ 118 | { 119 | "pattern": [ 120 | { 121 | "regexp": ".", 122 | "file": 1, 123 | "location": 2, 124 | "message": 3 125 | } 126 | ], 127 | "background": { 128 | "activeOnStart": true, 129 | "beginsPattern": ".", 130 | "endsPattern": ".", 131 | } 132 | } 133 | ] 134 | }, 135 | { 136 | "label": "Deploy and Debug on Zynq", 137 | "dependsOrder": "sequence", 138 | "dependsOn": [ 139 | "Build", 140 | "Copy Files to Zynq", 141 | "Launch gdbserver on Zynq" 142 | ], 143 | "group": { 144 | "kind": "build", 145 | "isDefault": false 146 | }, 147 | "type": "shell", 148 | "command": "echo Deploy and Debug completed." 149 | } 150 | ], 151 | "version": "2.0.0" 152 | } -------------------------------------------------------------------------------- /controller-software/core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16.0) 2 | 3 | 4 | # Setup compiler settings 5 | set(CMAKE_C_STANDARD 11) 6 | set(CMAKE_C_STANDARD_REQUIRED ON) 7 | set(CMAKE_C_EXTENSIONS ON) 8 | set(CMAKE_CXX_STANDARD 20) 9 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 10 | set(CMAKE_CXX_EXTENSIONS ON) 11 | set(PROJ_PATH ${CMAKE_CURRENT_SOURCE_DIR}) 12 | message("Build type: " ${CMAKE_BUILD_TYPE}) 13 | 14 | 15 | project(controller_core) 16 | enable_language(C CXX ASM) 17 | 18 | set(EXECUTABLE controller_core) 19 | 20 | # 21 | # List of source files to compile 22 | # 23 | file(GLOB_RECURSE sources_SRCS 24 | ${PROJ_PATH}/controller/src/*.cpp 25 | ${PROJ_PATH}/controller/src/*.c 26 | ${PROJ_PATH}/controller/fpga_module_drivers/src/*.cpp 27 | ${PROJ_PATH}/controller/fpga_module_drivers/src/*.c 28 | ) 29 | 30 | # Executable files 31 | add_executable(${EXECUTABLE} ${sources_SRCS}) 32 | 33 | # 34 | # Include directories 35 | # 36 | set(include_path_DIRS 37 | ${PROJ_PATH}/controller/inc 38 | ${PROJ_PATH}/controller/fpga_module_drivers/inc 39 | ) 40 | 41 | # Include paths 42 | target_include_directories(${EXECUTABLE} PRIVATE ${include_path_DIRS}) 43 | 44 | # copy all required files to the zynq directory 45 | add_custom_command(TARGET controller_core POST_BUILD 46 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 47 | $ 48 | ${CMAKE_CURRENT_SOURCE_DIR}/zynq_files/controller/bin 49 | ) -------------------------------------------------------------------------------- /controller-software/core/CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "configurePresets": [ 4 | { 5 | "name": "default", 6 | "hidden": true, 7 | "generator": "Ninja", 8 | "binaryDir": "${sourceDir}/build", 9 | "cacheVariables": {} 10 | }, 11 | { 12 | "name": "ZYNQ-Debug", 13 | "description": "Cross compile for ARM Cortex-A9 with NEON support", 14 | "inherits": "default", 15 | "toolchainFile": "${sourceDir}/cmake/zynq_debug.cmake", 16 | "cacheVariables": { 17 | "CMAKE_BUILD_TYPE": "Debug" 18 | } 19 | } 20 | ], 21 | "buildPresets": [ 22 | { 23 | "name": "ZYNQ-Debug", 24 | "configurePreset": "ZYNQ-Debug", 25 | "description": "Build debug version for ZYNQ", 26 | "jobs": 4 27 | } 28 | ], 29 | "testPresets": [] 30 | } 31 | -------------------------------------------------------------------------------- /controller-software/core/cmake/zynq_debug.cmake: -------------------------------------------------------------------------------- 1 | 2 | 3 | set(CMAKE_SYSTEM_NAME Linux) 4 | set(CMAKE_SYSTEM_PROCESSOR arm) 5 | 6 | # Specify the cross compiler 7 | set(CMAKE_C_COMPILER arm-linux-gnueabihf-gcc) 8 | set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++) 9 | 10 | # Specify the target architecture and NEON support 11 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=cortex-a9 -mfpu=neon -mfloat-abi=hard -g -O0") 12 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-psabi -mcpu=cortex-a9 -mfpu=neon -mfloat-abi=hard -g -O0") 13 | 14 | set(CMAKE_BUILD_TYPE Debug) 15 | 16 | # Optionally specify the sysroot if necessary 17 | # set(CMAKE_SYSROOT /path/to/sysroot) 18 | -------------------------------------------------------------------------------- /controller-software/core/controller/api_drivers/api_objects.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include // For smart pointers 3 | #include 4 | #include "shared_mem.h" 5 | #include "../json.hpp" 6 | 7 | 8 | #include "calls/machine_state.h" 9 | #include "calls/print_uint32.h" 10 | 11 | using json = nlohmann::json; 12 | 13 | #pragma once 14 | 15 | uint32_t get_new_call_object_from_call_id(std::vector>* calls, const api_call_id_t* api_call_id) { 16 | if(*api_call_id == api_call_id_t::DEFAULT){ 17 | calls->push_back(std::make_shared()); 18 | } 19 | else if(*api_call_id == api_call_id_t::MACHINE_STATE){ 20 | calls->push_back(std::make_shared()); 21 | } 22 | else if(*api_call_id == api_call_id_t::PRINT_UINT32){ 23 | calls->push_back(std::make_shared()); 24 | } 25 | else { 26 | return 1; // error: unknown call ID 27 | } 28 | 29 | return 0; 30 | } 31 | 32 | uint32_t create_new_call_obj_from_string(std::vector>* calls, std::string api_call_name, uint32_t* api_call_number) { 33 | if(api_call_name.compare("machine_state") == 0){ 34 | calls->push_back(std::make_shared()); 35 | } 36 | else if(api_call_name.compare("print_uint32") == 0){ 37 | calls->push_back(std::make_shared()); 38 | } 39 | else { 40 | return 1; 41 | } 42 | 43 | calls->back()->api_call_number = Base_API::web_to_controller_call_count + 1; 44 | *api_call_number = calls->back()->api_call_number; 45 | Base_API::web_to_controller_call_count++; 46 | calls->back()->persistent_web_mem[0] = Base_API::web_to_controller_call_count; 47 | 48 | return 0; 49 | } 50 | 51 | uint32_t remove_last_call_obj_from_string(std::vector>* calls, uint32_t* api_call_number) { // remove the last call object from the vector, used if the call object is not valid 52 | 53 | // decrement call numbers 54 | api_call_number--; 55 | Base_API::web_to_controller_call_count--; 56 | calls->back()->persistent_web_mem[0] = Base_API::web_to_controller_call_count; 57 | 58 | calls->pop_back(); // remove failed call object 59 | return 0; 60 | } -------------------------------------------------------------------------------- /controller-software/core/controller/api_drivers/calls/machine_state.h: -------------------------------------------------------------------------------- 1 | #include "base_call.h" 2 | 3 | #pragma once 4 | 5 | // get/set machione state 6 | class machine_state: public Base_API { 7 | private: 8 | enum machine_state_t { 9 | NONE, 10 | ON, 11 | OFF 12 | }; 13 | 14 | machine_state_t command_state = NONE; 15 | machine_state_t requested_state = NONE; 16 | machine_state_t current_state = NONE; 17 | 18 | 19 | public: 20 | machine_state(){ 21 | api_call_id = api_call_id_t::MACHINE_STATE; 22 | } 23 | 24 | unsigned int web_input_data(json* data) override { 25 | command_state = NONE; 26 | if(data->contains("commanded_state")){ 27 | 28 | if(data->at("commanded_state").is_string()){ 29 | if(data->at("commanded_state").get().compare("on") == 0){ 30 | command_state = ON; 31 | } 32 | else if(data->at("commanded_state").get().compare("off") == 0){ 33 | command_state = OFF; 34 | } 35 | else { 36 | return 1; 37 | } 38 | } 39 | else { 40 | return 1; 41 | } 42 | } 43 | 44 | return 0; 45 | } 46 | 47 | unsigned int web_output_data(json* data) override { 48 | data->emplace("call_name", "machine_state"); 49 | 50 | if(current_state == ON){ 51 | data->emplace("current_state", "on"); 52 | } 53 | else if(current_state == OFF){ 54 | data->emplace("current_state", "off"); 55 | } 56 | else if(current_state == NONE){ 57 | data->emplace("current_state", "none"); 58 | } 59 | else { 60 | data->emplace("current_state", "unknown"); 61 | } 62 | 63 | if(requested_state == ON){ 64 | data->emplace("requested_state", "on"); 65 | } 66 | else if(requested_state == OFF){ 67 | data->emplace("requested_state", "off"); 68 | } 69 | else if(requested_state == NONE){ 70 | data->emplace("requested_state", "none"); 71 | } 72 | else { 73 | data->emplace("requested_state", "unknown"); 74 | } 75 | 76 | if(command_state == ON){ 77 | data->emplace("commanded_state", "on"); 78 | } 79 | else if(command_state == OFF){ 80 | data->emplace("commanded_state", "off"); 81 | } 82 | else if(command_state == NONE){ 83 | data->emplace("commanded_state", "none"); 84 | } 85 | else { 86 | data->emplace("commanded_state", "unknown"); 87 | } 88 | 89 | 90 | return 0; 91 | } 92 | 93 | unsigned int web_write_to_shared_mem() override { 94 | // write the call info to shared memory 95 | 96 | uint32_t start_index = web_to_controller_data_mem_index; 97 | uint32_t size = sizeof(command_state); 98 | 99 | copy_to_web_to_controller_data_buffer(&command_state, sizeof(command_state)); 100 | 101 | update_web_to_controller_control_buffer(api_call_id_t::MACHINE_STATE, start_index, size); 102 | 103 | return 0; 104 | } 105 | 106 | unsigned int controller_read_from_shared_mem() override { 107 | // read the data from shared memory, layout must match the format set in web_write_to_shared_mem 108 | 109 | copy_from_web_to_controller_data_buffer(&command_state, sizeof(command_state)); 110 | 111 | return 0; 112 | } 113 | 114 | unsigned int controller_write_to_shared_mem() override { 115 | // write the call info to shared memory, layout must match web_read_from_shared_mem 116 | 117 | uint32_t start_index = controller_to_web_data_mem_index; 118 | uint32_t size = sizeof(command_state) + sizeof(current_state) + sizeof(requested_state); 119 | 120 | copy_to_controller_to_web_data_buffer(&command_state, sizeof(command_state)); 121 | copy_to_controller_to_web_data_buffer(¤t_state, sizeof(current_state)); 122 | copy_to_controller_to_web_data_buffer(&requested_state, sizeof(requested_state)); 123 | 124 | update_controller_to_web_control_buffer(api_call_id_t::MACHINE_STATE, start_index, size); 125 | 126 | return 0; 127 | } 128 | 129 | unsigned int web_read_from_shared_mem() override { 130 | // read the data from shared memory, layout must match the format set in controller_write_to_shared_mem 131 | 132 | copy_from_controller_to_web_data_buffer(&command_state, sizeof(command_state)); 133 | copy_from_controller_to_web_data_buffer(¤t_state, sizeof(current_state)); 134 | copy_from_controller_to_web_data_buffer(&requested_state, sizeof(requested_state)); 135 | 136 | return 0; 137 | } 138 | 139 | unsigned int run() override { 140 | // run the API call 141 | 142 | // check if the command state is different from the current state 143 | if (command_state != machine_state_t::NONE && command_state != current_state) { 144 | std::cout << "requested state changed to: " << command_state << "\n"; 145 | requested_state = command_state; 146 | 147 | current_state = requested_state; 148 | requested_state = machine_state_t::NONE; 149 | std::cout << "current state changed to: " << current_state << "\n"; 150 | } 151 | 152 | return 0; 153 | } 154 | }; 155 | -------------------------------------------------------------------------------- /controller-software/core/controller/api_drivers/calls/print_uint32.h: -------------------------------------------------------------------------------- 1 | #include "base_call.h" 2 | 3 | #pragma once 4 | 5 | // print value to the console on the controller side 6 | class print_uint32: public Base_API { 7 | private: 8 | uint32_t value = 0; 9 | 10 | public: 11 | print_uint32(){ 12 | api_call_id = api_call_id_t::PRINT_UINT32; 13 | } 14 | 15 | unsigned int web_input_data(json* data) override { 16 | if(data->contains("value")){ 17 | if(data->at("value").is_number_integer()){ 18 | value = data->at("value").get(); 19 | } 20 | else { 21 | return 1; 22 | } 23 | } 24 | else { 25 | return 1; 26 | } 27 | return 0; 28 | } 29 | 30 | unsigned int web_output_data(json* data) override { 31 | data->emplace("call_name", "print_uint32"); 32 | data->emplace("value", value); 33 | return 0; 34 | } 35 | 36 | unsigned int web_write_to_shared_mem() override { 37 | // write the call info to shared memory 38 | 39 | uint32_t start_index = web_to_controller_data_mem_index; 40 | uint32_t size = sizeof(value); 41 | 42 | copy_to_web_to_controller_data_buffer(&value, sizeof(value)); 43 | 44 | update_web_to_controller_control_buffer(api_call_id_t::PRINT_UINT32, start_index, size); 45 | 46 | return 0; 47 | } 48 | 49 | unsigned int controller_read_from_shared_mem() override { 50 | // read the data from shared memory, layout must match the format set in web_write_to_shared_mem 51 | 52 | copy_from_web_to_controller_data_buffer(&value, sizeof(value)); 53 | 54 | return 0; 55 | } 56 | 57 | unsigned int controller_write_to_shared_mem() override { 58 | // write the call info to shared memory, layout must match web_read_from_shared_mem 59 | 60 | uint32_t start_index = controller_to_web_data_mem_index; 61 | uint32_t size = sizeof(value); 62 | 63 | copy_to_controller_to_web_data_buffer(&value, sizeof(value)); 64 | 65 | update_controller_to_web_control_buffer(api_call_id_t::PRINT_UINT32, start_index, size); 66 | 67 | return 0; 68 | } 69 | 70 | unsigned int web_read_from_shared_mem() override { 71 | // read the data from shared memory, layout must match the format set in controller_write_to_shared_mem 72 | 73 | copy_from_controller_to_web_data_buffer(&value, sizeof(value)); 74 | 75 | return 0; 76 | } 77 | 78 | unsigned int run() override { 79 | // run the API call 80 | 81 | std::cout << "value: " << value << "\n"; 82 | 83 | return 0; 84 | } 85 | }; 86 | -------------------------------------------------------------------------------- /controller-software/core/controller/api_drivers/shared_mem.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #pragma once 11 | 12 | class shared_mem 13 | { 14 | private: 15 | const unsigned int data_buffer_size = 256 * 4; // size of shared memory buffer for data (64 32-bit values) 16 | const unsigned int control_buffer_size = 32 * 4 * 4; // size of shared memory buffer for control info (able to hold 32 API calls) 17 | 18 | void* web_to_controller_data_mem = nullptr; 19 | void* web_to_controller_control_mem = nullptr; 20 | void* controller_to_web_data_mem = nullptr; 21 | void* controller_to_web_control_mem = nullptr; 22 | 23 | void* persistent_web_mem = nullptr; 24 | 25 | int web_to_controller_data_map; 26 | int web_to_controller_control_map; 27 | int controller_to_web_data_map; 28 | int controller_to_web_control_map; 29 | 30 | int persistent_web_map; 31 | 32 | unsigned int create_shared_mem(unsigned int size, void *&mem, int &shm_fd, std::string shm_name, int prot_flags = PROT_READ | PROT_WRITE); 33 | unsigned int open_shared_mem(unsigned int size, void *&mem, int &shm_fd, std::string shm_name, int prot_flags = PROT_READ | PROT_WRITE); 34 | 35 | public: 36 | 37 | void* get_web_to_controller_data_mem() const { return web_to_controller_data_mem; } 38 | void* get_web_to_controller_control_mem() const { return web_to_controller_control_mem; } 39 | void* get_controller_to_web_data_mem() const { return controller_to_web_data_mem; } 40 | void* get_controller_to_web_control_mem() const { return controller_to_web_control_mem; } 41 | 42 | void* get_persistent_web_mem() const { return persistent_web_mem; } 43 | 44 | ~shared_mem(); 45 | 46 | unsigned int controller_create_shared_mem(); 47 | unsigned int web_create_shared_mem(); 48 | 49 | unsigned int get_data_buffer_size() { return data_buffer_size; } 50 | unsigned int get_control_buffer_size() { return control_buffer_size; } 51 | 52 | void unmap_shared_mem(); 53 | void close_shared_mem(); 54 | }; 55 | 56 | unsigned int shared_mem::open_shared_mem(unsigned int size, void *&mem, int &shm_fd, std::string shm_name, int prot_flags) { 57 | 58 | // Ensure name starts with '/' 59 | if (shm_name.empty() || shm_name[0] != '/') { 60 | shm_name = "/" + shm_name; 61 | } 62 | 63 | // Open the existing shared memory object 64 | shm_fd = shm_open(shm_name.c_str(), O_RDWR, 0666); 65 | if (shm_fd == -1) { 66 | std::cerr << "Could not open shared memory object: " << strerror(errno) << std::endl; 67 | return 1; 68 | } 69 | 70 | // Map the shared memory object into the process's address space 71 | mem = mmap(NULL, size, prot_flags, MAP_SHARED, shm_fd, 0); 72 | if (mem == MAP_FAILED) { 73 | std::cerr << "Could not map shared memory object: " << strerror(errno) << std::endl; 74 | close(shm_fd); 75 | return 1; 76 | } 77 | 78 | // // set memory to all zero if we have write access 79 | // if(prot_flags & PROT_WRITE){ 80 | // memset(mem, 0, size); 81 | // } 82 | 83 | return 0; 84 | } 85 | 86 | unsigned int shared_mem::create_shared_mem(unsigned int size, void *&mem, int &shm_fd, std::string shm_name, int prot_flags) { 87 | 88 | // Ensure name starts with '/' 89 | if (shm_name.empty() || shm_name[0] != '/') { 90 | shm_name = "/" + shm_name; 91 | } 92 | 93 | // Open or create the shared memory object 94 | shm_fd = shm_open(shm_name.c_str(), O_CREAT | O_RDWR, 0666); 95 | if (shm_fd == -1) { 96 | std::cerr << "Could not open shared memory object: " << strerror(errno) << std::endl; 97 | return 1; 98 | } 99 | 100 | // Set the size of the shared memory object 101 | if (ftruncate(shm_fd, size) == -1) { 102 | std::cerr << "Could not set size of shared memory object: " << strerror(errno) << std::endl; 103 | close(shm_fd); 104 | shm_unlink(shm_name.c_str()); 105 | return 1; 106 | } 107 | 108 | // Map the shared memory object into the process's address space 109 | mem = mmap(NULL, size, prot_flags, MAP_SHARED, shm_fd, 0); 110 | if (mem == MAP_FAILED) { 111 | std::cerr << "Could not map shared memory object: " << strerror(errno) << std::endl; 112 | close(shm_fd); 113 | shm_unlink(shm_name.c_str()); 114 | return 1; 115 | } 116 | 117 | // set memory to all zero if we have write access 118 | if(prot_flags & PROT_WRITE){ 119 | memset(mem, 0, size); 120 | } 121 | 122 | return 0; 123 | } 124 | 125 | unsigned int shared_mem::controller_create_shared_mem(){ // called from the controller side 126 | if(create_shared_mem(data_buffer_size, web_to_controller_data_mem, web_to_controller_data_map, "web_to_controller_data_mem", PROT_READ | PROT_WRITE) != 0){ // read and write so we can clear the buffer on startup 127 | return 1; 128 | } 129 | if(create_shared_mem(control_buffer_size, web_to_controller_control_mem, web_to_controller_control_map, "web_to_controller_control_mem", PROT_READ | PROT_WRITE) != 0){ // read and write so we can clear the buffer on startup 130 | return 1; 131 | } 132 | if(create_shared_mem(data_buffer_size, controller_to_web_data_mem, controller_to_web_data_map, "controller_to_web_data_mem", PROT_WRITE) != 0){ 133 | return 1; 134 | } 135 | if(create_shared_mem(control_buffer_size, controller_to_web_control_mem, controller_to_web_control_map, "controller_to_web_control_mem", PROT_WRITE) != 0){ 136 | return 1; 137 | } 138 | if(create_shared_mem(sizeof(uint32_t)*4, persistent_web_mem, persistent_web_map, "persistent_web_mem", PROT_READ | PROT_WRITE) != 0){ 139 | return 1; 140 | } 141 | return 0; 142 | } 143 | 144 | unsigned int shared_mem::web_create_shared_mem(){ // called from the web side 145 | if(open_shared_mem(data_buffer_size, web_to_controller_data_mem, web_to_controller_data_map, "web_to_controller_data_mem", PROT_WRITE) != 0){ 146 | return 1; 147 | } 148 | if(open_shared_mem(control_buffer_size, web_to_controller_control_mem, web_to_controller_control_map, "web_to_controller_control_mem", PROT_WRITE) != 0){ 149 | return 1; 150 | } 151 | if(open_shared_mem(data_buffer_size, controller_to_web_data_mem, controller_to_web_data_map, "controller_to_web_data_mem", PROT_READ) != 0){ 152 | return 1; 153 | } 154 | if(open_shared_mem(control_buffer_size, controller_to_web_control_mem, controller_to_web_control_map, "controller_to_web_control_mem", PROT_READ) != 0){ 155 | return 1; 156 | } 157 | if(open_shared_mem(sizeof(uint32_t)*4, persistent_web_mem, persistent_web_map, "persistent_web_mem", PROT_READ | PROT_WRITE) != 0){ 158 | return 1; 159 | } 160 | return 0; 161 | } 162 | 163 | void shared_mem::unmap_shared_mem(){ 164 | munmap(web_to_controller_data_mem, data_buffer_size); 165 | munmap(controller_to_web_data_mem, data_buffer_size); 166 | munmap(web_to_controller_control_mem, control_buffer_size); 167 | munmap(controller_to_web_control_mem, control_buffer_size); 168 | munmap(persistent_web_mem, sizeof(uint32_t)*4); 169 | } 170 | 171 | void shared_mem::close_shared_mem(){ 172 | close(web_to_controller_data_map); 173 | close(controller_to_web_data_map); 174 | close(web_to_controller_control_map); 175 | close(controller_to_web_control_map); 176 | close(persistent_web_map); 177 | } 178 | 179 | shared_mem::~shared_mem(){ 180 | } 181 | -------------------------------------------------------------------------------- /controller-software/core/controller/api_drivers/test_api: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExcessiveMotion/controller-software/0503bb292ecb54b9c41ecbcdc22a93bbeb4c36b0/controller-software/core/controller/api_drivers/test_api -------------------------------------------------------------------------------- /controller-software/core/controller/api_drivers/test_api.cpp: -------------------------------------------------------------------------------- 1 | #include "web_api.h" 2 | #include 3 | #include 4 | 5 | int main() { 6 | web_api api; 7 | 8 | uint32_t api_call_number; 9 | 10 | for(int c=0; c<1; c++){ 11 | 12 | for(int i=0; i<8; i++){ 13 | json call = { 14 | {"call_name", "print_uint32"}, 15 | {"value", i + 8*c} 16 | }; 17 | //std::cout << "value: " << i + 8*c << std::endl; 18 | api.add_call(&call, &api_call_number); 19 | } 20 | json call = { 21 | {"call_name", "print_uint32"}, 22 | {"bad_key", 0} 23 | }; 24 | //api.add_call(&call, &api_call_number); 25 | 26 | api.write_web_calls_to_controller(); 27 | 28 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 29 | json response; 30 | api.get_completed_calls(&response); 31 | 32 | std::cout << response.dump(4) << std::endl; // Pretty print with indentation 33 | 34 | } 35 | return 0; 36 | } -------------------------------------------------------------------------------- /controller-software/core/controller/api_drivers/test_controller: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExcessiveMotion/controller-software/0503bb292ecb54b9c41ecbcdc22a93bbeb4c36b0/controller-software/core/controller/api_drivers/test_controller -------------------------------------------------------------------------------- /controller-software/core/controller/api_drivers/test_controller.cpp: -------------------------------------------------------------------------------- 1 | #include "controller_api.h" 2 | #include 3 | #include 4 | 5 | 6 | int main() { 7 | 8 | controller_api api; 9 | 10 | while(1){ 11 | 12 | api.get_new_call(); 13 | api.run_calls(); 14 | 15 | std::this_thread::sleep_for(std::chrono::milliseconds(1)); 16 | } 17 | return 0; 18 | } -------------------------------------------------------------------------------- /controller-software/core/controller/api_drivers/test_controller.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExcessiveMotion/controller-software/0503bb292ecb54b9c41ecbcdc22a93bbeb4c36b0/controller-software/core/controller/api_drivers/test_controller.exe -------------------------------------------------------------------------------- /controller-software/core/controller/fpga_module_drivers/inc/em_serial_controller.h: -------------------------------------------------------------------------------- 1 | #include "fpga_module_driver_factory.h" 2 | #include "register_helper.h" 3 | 4 | 5 | #pragma once 6 | 7 | class em_serial_device{ 8 | public: 9 | em_serial_device(Address_Map_Loader* loader, uint8_t device_number); 10 | 11 | uint32_t set_address(uint32_t address); 12 | uint32_t get_address(uint16_t* address); 13 | uint32_t set_enabled(bool enabled); 14 | uint32_t get_enabled(bool* enabled); 15 | uint32_t set_cyclic_data_enabled(bool enabled); 16 | bool get_cyclic_data_enabled(); 17 | bool get_sequential_cmds_complete(); 18 | bool get_cyclic_config_complete(); 19 | 20 | 21 | // "address" in below functions is the device's serial register address 22 | uint32_t sequential_write(uint16_t address, void* data, uint8_t size); 23 | uint32_t sequential_read(uint16_t address, void* data, uint8_t size); 24 | 25 | uint32_t configure_cyclic_read(uint16_t address, uint8_t size); 26 | //uint32_t disable_cyclic_read(uint16_t address); // TODO: implement 27 | 28 | uint32_t configure_cyclic_write(uint16_t address, uint8_t size); 29 | //uint32_t disable_cyclic_write(uint16_t address); // TODO: implement 30 | 31 | uint32_t get_cyclic_read_data_mem_address(uint16_t address, uint8_t* node_address, uint16_t* bram_address); // get node and bram address for cyclic data, used for DMA instructions 32 | uint32_t get_cyclic_write_data_mem_address(uint16_t address, uint8_t* node_address, uint16_t* bram_address); // get node and bram address for cyclic data, used for DMA instructions 33 | 34 | uint32_t set_cyclic_read_pointer(uint16_t address, void** data); // automatically move cyclic data to the pointer 35 | uint32_t set_cyclic_write_pointer(uint16_t address, void** data); // automatically move pointer data to the cyclic location 36 | 37 | uint32_t run(); // run the device, must be called at each FPGA update 38 | 39 | 40 | 41 | // TEMPORARY: for testing 42 | void* test_read = nullptr; 43 | 44 | 45 | private: 46 | Address_Map_Loader* loader = nullptr; 47 | 48 | struct sequential_cmd{ 49 | uint16_t address; // serial register address to read/write 50 | uint32_t data = 0; // data to write or read into 51 | uint8_t size; // size of the data in bytes 52 | bool write; 53 | bool* complete_flag = nullptr; // use this to let external code know when the command is complete 54 | bool* complete_flag_inverted = nullptr; // use this to let external code know when the command is complete 55 | }; 56 | const uint8_t max_outstanding_cmds = 64; // TODO: get this from some config? 57 | std::vector sequential_cmds; 58 | 59 | #define MAX_CYCLIC_REGS 64 // TODO: get this from some config 60 | uint16_t max_cyclic_registers = MAX_CYCLIC_REGS; 61 | bool cyclic_read_enabled[MAX_CYCLIC_REGS]; 62 | bool cyclic_write_enabled[MAX_CYCLIC_REGS]; 63 | 64 | struct controller_cyclic_config{ // configure a cyclic read or write 65 | 66 | // fpga module configs 67 | uint8_t size = 0; 68 | uint8_t offset = 0; 69 | 70 | // device configs 71 | uint16_t address = 0; 72 | 73 | // controller configs 74 | uint16_t packet_data_start_byte = 0; // where the data starts in the packet (absolute byte index) 75 | uint16_t packet_data_end_byte = 0; // where the data ends in the packet (absolute byte index) 76 | Register* cyclic_data_register = nullptr; // the register that holds the cyclic data 77 | }; 78 | 79 | std::vector read_cyclic_configs; 80 | std::vector write_cyclic_configs; 81 | 82 | struct raw_cyclic_config{ 83 | uint8_t read_size = 0; 84 | uint8_t write_size = 0; 85 | uint8_t read_offset = 0; 86 | uint8_t write_offset = 0; 87 | } 88 | raw_cyclic_configs[MAX_CYCLIC_REGS]; 89 | std::vector outstanding_cyclic_configs; 90 | 91 | uint32_t address = 0; 92 | bool enabled = false; 93 | bool cyclic_data_enabled = false; 94 | bool cyclic_config_complete = true; 95 | bool sequential_cmds_complete = true; 96 | bool initial_config_complete = false; 97 | 98 | struct registers{ // registers needed to control and configure the device 99 | Register* control = nullptr; 100 | Register* control_enable = nullptr; 101 | Register* control_enable_cyclic_data = nullptr; 102 | Register* control_rx_cyclic_packet_size = nullptr; 103 | 104 | Register* status = nullptr; 105 | Register* status_no_response = nullptr; 106 | Register* status_response_not_finished = nullptr; 107 | Register* status_crc_invalid = nullptr; 108 | 109 | Register* cyclic_configs[MAX_CYCLIC_REGS]; 110 | Register* cyclic_reads[MAX_CYCLIC_REGS]; 111 | Register* cyclic_writes[MAX_CYCLIC_REGS]; 112 | 113 | Dynamic_Register* cyclic_config; 114 | Register* cyclic_config_reg = nullptr; // from the dynamic register 115 | Register* cyclic_config_read_size = nullptr; 116 | Register* cyclic_config_write_size = nullptr; 117 | Register* cyclic_config_read_offset = nullptr; 118 | Register* cyclic_config_write_offset = nullptr; 119 | 120 | // TODO: instead of saving every register object, maybe just save the PL addresses to save space? 121 | 122 | 123 | } regs; 124 | 125 | struct device_info{ 126 | uint16_t hardware_type_addr = 0; 127 | uint16_t hardware_version_addr = 1; 128 | uint16_t firmware_version_addr = 2; 129 | uint16_t enable_cyclic_data_addr = 0xffff; 130 | uint16_t cyclic_read_address_0_addr = 0xffff; 131 | uint16_t cyclic_write_address_0_addr = 0xffff; 132 | 133 | uint16_t cyclic_addresses = 0; 134 | 135 | uint32_t hardware_type = 0; 136 | uint32_t hardware_version = 0; 137 | uint32_t firmware_version = 0; 138 | } dev_info; 139 | 140 | uint32_t add_sequential_cmd(uint16_t address, void* data, uint8_t size, bool write, bool* complete_flag, bool* complete_flag_inverted); 141 | 142 | struct faults{ 143 | bool no_response = false; 144 | bool response_not_finished = false; 145 | bool crc_invalid = false; 146 | } fault; 147 | uint32_t consecutive_packet_errors = 0; 148 | uint32_t consecutive_unknown_packet_errors = 0; 149 | 150 | uint32_t run_sequential_cmds(); 151 | uint32_t run_cylic_config(); 152 | 153 | }; 154 | 155 | class em_serial_controller : public base_driver { 156 | public: 157 | uint32_t load_config(json config, std::vector* instructions) override; 158 | 159 | uint32_t run() override; 160 | 161 | uint32_t configure_baud_rate(uint32_t baud_rate); // all devices on port, minimum 115200 162 | uint32_t enable_transfers(bool enable); // enable/disable transfers for all enabled devices 163 | uint32_t autoconfigure_devices(); // autoconfigure all devices on the port 164 | 165 | //int32_t* cmd_q_current_milliamps = nullptr; 166 | 167 | private: 168 | 169 | Register* control = nullptr; 170 | Register* start_transfers = nullptr; 171 | Register* bit_length = nullptr; 172 | Register* status = nullptr; 173 | Register* status_update_busy = nullptr; 174 | Register* status_update_done = nullptr; 175 | Register* status_update_error = nullptr; 176 | 177 | std::vector devices; 178 | 179 | }; -------------------------------------------------------------------------------- /controller-software/core/controller/fpga_module_drivers/inc/fanuc_encoders.h: -------------------------------------------------------------------------------- 1 | #include "fpga_module_driver_factory.h" 2 | #include "register_helper.h" 3 | 4 | 5 | #pragma once 6 | 7 | class fanuc_encoders : public base_driver { 8 | public: 9 | uint32_t load_config(json config, std::vector* instructions) override; 10 | 11 | uint32_t run() override; 12 | 13 | private: 14 | 15 | Register* multiturn_count = nullptr; 16 | Register* singleturn_count = nullptr; 17 | Register* commutation_count = nullptr; 18 | Register* crc_error = nullptr; 19 | Register* no_response = nullptr; 20 | Register* unindexed = nullptr; 21 | Register* battery_fail = nullptr; 22 | Register* done = nullptr; 23 | 24 | struct encoder_data{ 25 | uint32_t multiturn_count = 0; 26 | uint32_t singleturn_count = 0; 27 | uint16_t commutation_angle = 0; 28 | bool battery_fail = false; 29 | bool unindexed = false; 30 | bool no_resonse = false; 31 | bool crc_error = false; 32 | bool done = false; 33 | }; 34 | 35 | uint32_t old_microseconds = 0; 36 | 37 | std::vector encoders; 38 | 39 | }; -------------------------------------------------------------------------------- /controller-software/core/controller/fpga_module_drivers/inc/global_timers.h: -------------------------------------------------------------------------------- 1 | #include "fpga_module_driver_factory.h" 2 | #include "register_helper.h" 3 | 4 | 5 | #pragma once 6 | 7 | class global_timers : public base_driver { 8 | public: 9 | uint32_t load_config(json config, std::vector* instructions) override; 10 | 11 | uint32_t run() override; 12 | 13 | private: 14 | 15 | std::vector timer_values; 16 | 17 | }; -------------------------------------------------------------------------------- /controller-software/core/controller/fpga_module_drivers/inc/serial_interface_card.h: -------------------------------------------------------------------------------- 1 | #include "fpga_module_driver_factory.h" 2 | #include "register_helper.h" 3 | 4 | 5 | #pragma once 6 | 7 | class serial_interface_card : public base_driver { 8 | public: 9 | serial_interface_card(); 10 | ~serial_interface_card(); 11 | 12 | uint32_t load_config(json config, std::vector* instructions) override; 13 | //uint32_t get_base_instructions(std::vector* instructions) override; 14 | 15 | uint32_t run() override; 16 | 17 | private: 18 | 19 | // TODO: probably should make a way to free this memory apon destruction (if we allow reconfig without restarting) 20 | struct registers2{ 21 | Register* rs485_mode_enable; 22 | Register* rs422_mode_enable; 23 | Register* quadrature_mode_enable; 24 | Register* i2c_config_read_mode; 25 | Register* i2c_config_device_address; 26 | Register* i2c_config_reg_address; 27 | Register* i2c_config_data_length; 28 | Register* i2c_config_start; 29 | Register* i2c_status_busy; 30 | Register* i2c_status_error; 31 | Register* i2c_data_rx; 32 | Register* i2c_data_tx; 33 | 34 | } regs2; 35 | 36 | struct i2c_transfer{ 37 | uint8_t device_address = 0; 38 | uint8_t reg_address = 0; 39 | uint32_t* data = nullptr; 40 | uint8_t data_length = 0; 41 | bool read = false; 42 | }; 43 | 44 | struct io_expander_data{ 45 | uint16_t configuration = 0; 46 | uint16_t inputs = 0; 47 | uint16_t outputs = 0; 48 | }; 49 | 50 | io_expander_data io_expander_data[3]; // data for the 3 io expanders 51 | 52 | enum port_mode : uint8_t { 53 | RS485 = 0, 54 | RS422 = 1, 55 | //QUADRATURE = 2 // not implemented yet 56 | }; 57 | 58 | port_mode port_modes[10]; // current port modes for the 10 ports 59 | 60 | // TODO: make a separate class for i2c 61 | 62 | std::vector i2c_transfers; // list of i2c transfers to be done 63 | uint8_t last_i2c_transfer = 0; // index of the last i2c transfer sent 64 | uint32_t i2c_consecutive_error_count = 0; 65 | 66 | void run_i2c_transfers(); 67 | 68 | uint32_t configure_port_mode(uint8_t port, port_mode mode); 69 | 70 | enum led_mode : uint8_t { 71 | OFF = 0, 72 | ON = 1, 73 | BLINK_SLOW = 2, 74 | BLINK_MED = 3, 75 | BLINK_FAST = 4 76 | }; 77 | 78 | led_mode led_modes[3]; // current led modes for the 3 leds 79 | 80 | bool power_out_5v5_1_ok = false; 81 | bool power_out_5v5_2_ok = false; 82 | bool power_out_24v_ok = false; 83 | bool power_out_enable = false; 84 | 85 | 86 | }; -------------------------------------------------------------------------------- /controller-software/core/controller/fpga_module_drivers/src/fanuc_encoders.cpp: -------------------------------------------------------------------------------- 1 | #include "fanuc_encoders.h" 2 | 3 | static Driver_Registrar registrar("fanuc_encoders"); 4 | 5 | uint32_t fanuc_encoders::load_config(json config, std::vector* instructions){ 6 | 7 | if(!load_json_value(config, "node_address", &node_address)){ 8 | std::cerr << "Failed to load node address" << std::endl; 9 | return 1; 10 | } 11 | 12 | // configure registers 13 | Address_Map_Loader loader; 14 | loader.setup(&config, &base_mem, node_address, instructions); 15 | 16 | 17 | auto encoder = loader.get_group("encoder", 0); 18 | multiturn_count = encoder->get_register("multiturn_count", 0); 19 | loader.sync_with_PS(multiturn_count); 20 | 21 | singleturn_count = encoder->get_register("singleturn_count", 0); 22 | loader.sync_with_PS(singleturn_count); 23 | commutation_count = encoder->get_register("commutation_count", 0); 24 | loader.sync_with_PS(commutation_count); 25 | auto status = encoder->get_register("status", 0); 26 | loader.sync_with_PS(status); 27 | battery_fail = status->get_register("battery_fail"); 28 | unindexed = status->get_register("unindexed"); 29 | no_response = status->get_register("no_response"); 30 | crc_error = status->get_register("crc_fail"); 31 | done = status->get_register("done"); 32 | 33 | // test print instructions 34 | // for (auto& inst : *instructions){ 35 | // std::cout << "Instruction: " << inst << std::endl; 36 | // } 37 | 38 | encoder_pos = singleturn_count->get_raw_data_ptr(); 39 | encoder_multiturn_count = multiturn_count->get_raw_data_ptr(); 40 | 41 | return 0; 42 | } 43 | 44 | uint32_t fanuc_encoders::run(){ 45 | 46 | if(*microseconds > old_microseconds + 1000000){ 47 | // std::cout << "Encoder multiturn count: " << multiturn_count->get_value() << std::endl; 48 | std::cout << "Encoder single turn count: " << (singleturn_count->get_value()) << std::endl; 49 | // std::cout << "Encoder commutation count: " << commutation_count->get_value() << std::endl; 50 | //std::cout << "Encoder CRC error: " << crc_error->get_value() << std::endl; 51 | //std::cout << "Encoder no response: " << no_response->get_value() << std::endl; 52 | //std::cout << "Encoder unindexed: " << unindexed->get_value() << std::endl; 53 | //std::cout << "Encoder battery fail: " << battery_fail->get_value() << std::endl; 54 | //std::cout << "Encoder done: " << done->get_value() << std::endl << std::endl; 55 | old_microseconds = *microseconds; 56 | } 57 | 58 | return 0; 59 | } -------------------------------------------------------------------------------- /controller-software/core/controller/fpga_module_drivers/src/global_timers.cpp: -------------------------------------------------------------------------------- 1 | #include "global_timers.h" 2 | 3 | static Driver_Registrar registrar("global_timers"); 4 | 5 | uint32_t global_timers::load_config(json config, std::vector* instructions){ 6 | 7 | if(!load_json_value(config, "node_address", &node_address)){ 8 | std::cerr << "Failed to load node address" << std::endl; 9 | return 1; 10 | } 11 | 12 | // configure registers 13 | Address_Map_Loader loader; 14 | loader.setup(&config, &base_mem, node_address, instructions); 15 | 16 | // timer_values.push_back(loader.get_register("counter", 0)); 17 | // loader.sync_with_PS(timer_values[0]); 18 | 19 | // timer_values[0]->set_value(100); 20 | 21 | // test print instructions 22 | // for (auto& inst : *instructions){ 23 | // std::cout << "Instruction: " << inst << std::endl; 24 | // } 25 | 26 | return 0; 27 | } 28 | 29 | uint32_t global_timers::run(){ 30 | // do nothing for now 31 | return 0; 32 | } -------------------------------------------------------------------------------- /controller-software/core/controller/inc/controller.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "controller_api.h" 9 | #include 10 | #include "fpga_interface.h" 11 | 12 | 13 | class Controller { 14 | private: 15 | 16 | bool quit = false; // flag to exit the controller 17 | 18 | static std::atomic pause_noncritical_thread; // flag to pause the noncritical thread 19 | static std::condition_variable noncritical_cv; 20 | static std::mutex noncritical_mtx; 21 | 22 | uint32_t noncritical_pause_timeout_us = 10; // timeout for noncritical thread to pause 23 | 24 | // FPGA driver 25 | Fpga_Interface fpga; 26 | 27 | // API driver 28 | static controller_api api; 29 | std::thread noncritical_thread; 30 | 31 | // node core 32 | Node_Core node_core; 33 | 34 | uint32_t critical_calls(){ // realtime calls that must be completed each cycle 35 | // realtime loop at a set frequency 36 | 37 | // run low level fpga drivers 38 | 39 | 40 | 41 | // update global node variables from FPGA mem 42 | 43 | // run node network 44 | node_core.run_update(); 45 | 46 | // update FPGA mem from global node variables 47 | // signal to FPGA that update is done 48 | return 0; 49 | } 50 | 51 | static uint32_t noncritical_calls(Controller* controller){ // calls that can be delayed if needed 52 | // noncritical calls 53 | if(api.get_new_call() == 256){ 54 | return 256; 55 | } 56 | 57 | if(api.run_calls() == 256){ 58 | return 256; 59 | } 60 | 61 | return 0; 62 | } 63 | 64 | public: 65 | Controller(){ 66 | /* 67 | initialize: 68 | node core 69 | API driver 70 | FPGA interface (OCM) 71 | 72 | load configs (main config, node networks, motion layout, electrical layout, FPGA bitstream) 73 | configure FPGA bitstream 74 | configure low level device drivers 75 | configure FPGA DMA 76 | configure devices 77 | configure node networks 78 | 79 | setup update flag timer (fall back if FPGA is not triggering updates) 80 | 81 | */ 82 | 83 | fpga.initialize("/home/em-os/controller/config/fpga_configs/bit_files/bitfile.bit.bin"); 84 | fpga.set_update_frequency(1000); 85 | 86 | api.set_pause_flag(&pause_noncritical_thread); 87 | noncritical_thread = std::thread(noncritical_calls, this); // TODO: this should be a low priority thread 88 | 89 | } 90 | 91 | void run(){ 92 | 93 | 94 | uint32_t ret = 0; 95 | while(!quit){ 96 | 97 | // wait for update trigger 98 | if(fpga.wait_for_update() != 0){ 99 | std::cerr << "Error: FPGA update failed" << std::endl; 100 | quit = true; 101 | break; 102 | } 103 | 104 | // Signal noncritical thread to pause 105 | pause_noncritical_thread.store(true); 106 | 107 | // Wait for noncritical thread to pause 108 | auto wait_start = std::chrono::steady_clock::now(); 109 | bool paused = false; 110 | while (std::chrono::steady_clock::now() - wait_start < std::chrono::microseconds(noncritical_pause_timeout_us)) { 111 | if (!pause_noncritical_thread.load() || noncritical_thread.joinable()) { 112 | paused = true; 113 | break; 114 | } 115 | std::this_thread::sleep_for(std::chrono::microseconds(1)); 116 | } 117 | 118 | if (!paused) { 119 | std::cerr << "Non-critical thread did not pause in time, running critical update anyway" << std::endl; 120 | } 121 | 122 | void critical_calls(); 123 | 124 | update_duration_us = std::chrono::duration_cast(std::chrono::steady_clock::now() - last_trigger).count(); 125 | remaining_update_time_us = software_update_period_us - update_duration_us; 126 | std::cout << "Update duration: " << update_duration_us << "us" << std::endl; 127 | 128 | // if api handler is done, join then restart (might make this run at a lower update rate than the base frequency) 129 | if(noncritical_thread.joinable()){ 130 | noncritical_thread.join(); 131 | noncritical_thread = std::thread(noncritical_calls, this); 132 | } 133 | // Resume worker thread 134 | pause_noncritical_thread.store(false); 135 | noncritical_cv.notify_one(); 136 | 137 | } 138 | 139 | } 140 | 141 | uint32_t set_software_update_period_us(uint32_t period_us){ 142 | if(period_us > max_update_period_us || period_us < min_update_period_us){ 143 | std::cerr << "Error: update period out of range" << std::endl; 144 | return 1; 145 | } 146 | software_update_period_us = period_us; 147 | return 0; 148 | } 149 | 150 | ~Controller(){ 151 | } 152 | }; 153 | 154 | 155 | controller_api Controller::api; 156 | 157 | std::atomic Controller::pause_noncritical_thread; 158 | std::condition_variable Controller::noncritical_cv; 159 | std::mutex Controller::noncritical_mtx; 160 | -------------------------------------------------------------------------------- /controller-software/core/controller/inc/fpga_instructions.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | 6 | 7 | enum instruction_type : uint8_t { 8 | END = 0, 9 | NOP = 1, 10 | COPY = 2 11 | }; 12 | 13 | struct fpga_mem{ // memory allocated to the driver by the FPGA manager 14 | uint32_t software_PS_PL_size = 0; // must be in 4 byte increments 15 | uint32_t hardware_PS_PL_size = 0; // in 32 bit words 16 | void* software_PS_PL_ptr = nullptr; // pointer in user space 17 | uint32_t hardware_PS_PL_mem_offset = 0; // offset in the PS->PL memory, used for instructions 18 | 19 | uint32_t software_PL_PS_size = 0; // must be in 4 byte increments 20 | uint32_t hardware_PL_PS_size = 0; // in 32 bit words 21 | void* software_PL_PS_ptr = nullptr; // pointer in user space 22 | uint32_t hardware_PL_PS_mem_offset = 0; // offset in the PS->PL memory, used for instructions 23 | }; 24 | 25 | static uint64_t create_instruction_END(){ 26 | return ((uint64_t)instruction_type::END << 48); 27 | } 28 | 29 | static uint64_t create_instruction_NOP(){ 30 | return ((uint64_t)instruction_type::NOP << 48); 31 | } 32 | 33 | static uint64_t create_instruction_COPY(uint8_t src_node, uint16_t src_addr, uint8_t dst_node, uint16_t dst_addr){ 34 | // you may copy to/from any address in node 0 (memory which is synced with the PS), 35 | // but only the lower half is copied to the PS, the upper half is copied from the PS to PL 36 | // the halfway offset is determined by DATA_MEMORY_SIZE in the config (in 32 bit words) 37 | // node addresses are in single increments, but contain 32 bit words 38 | std::cout << "src_node: " << (uint64_t)src_node << " src_addr: " << (uint64_t)src_addr << " dst_node: " << (uint64_t)dst_node << " dst_addr: " << (uint64_t)dst_addr << std::endl; 39 | return ((uint64_t)src_node << 0) | ((uint64_t)dst_node << 8) | ((uint64_t)src_addr << 16) | ((uint64_t)dst_addr << 32) | ((uint64_t)instruction_type::COPY << 48); 40 | } -------------------------------------------------------------------------------- /controller-software/core/controller/inc/fpga_interface.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #pragma once 6 | 7 | struct fpga_mem_layout { 8 | 9 | uint32_t OCM_BASE_ADDR=0; 10 | uint32_t OCM_SIZE=0; 11 | 12 | uint32_t PS_to_PL_control_base_addr_offset=0; 13 | uint32_t PS_to_PL_control_size=0; // size of the memory in 8 bit words 14 | uint32_t PL_to_PS_control_base_addr_offset=0; 15 | uint32_t PL_to_PS_control_size=0; // size of the memory in 8 bit words 16 | 17 | uint32_t PS_to_PL_data_base_addr_offset=0; 18 | uint32_t PS_to_PL_data_size=0; // size of the memory in 8 bit words 19 | uint32_t PL_to_PS_data_base_addr_offset=0; 20 | uint32_t PL_to_PS_data_size=0; // size of the memory in 8 bit words 21 | 22 | uint32_t PS_to_PL_dma_instructions_base_addr_offset=0; 23 | uint32_t PS_to_PL_dma_instructions_size=0; // size of the memory in 8 bit words 24 | 25 | uint32_t data_memory_size=0; // size of the data memory in 32 bit words 26 | }; 27 | 28 | 29 | class Fpga_Interface { 30 | public: 31 | Fpga_Interface(); 32 | ~Fpga_Interface(); 33 | 34 | uint32_t initialize(fpga_mem_layout mem_layout, std::string bitstreamPath); // resets shared memory and loads bitstream 35 | 36 | 37 | // get pointers to memory accessible by the FPGA 38 | // these should ONLY be used after the FPGA has finished reading/writing to the memory and the fpga_busy flag is cleared 39 | // note that if the FPGA memory becomes unallocated, these functions will throw errors, but the previously returned pointers will likely lead to segfaults 40 | void* get_PS_to_PL_control_pointer(uint32_t offset); // should only be written to 41 | void* get_PL_to_PS_control_pointer(uint32_t offset); // should only be read from 42 | 43 | void* get_PS_to_PL_data_pointer(uint32_t offset); // should only be written to 44 | void* get_PL_to_PS_data_pointer(uint32_t offset); // should only be read from 45 | 46 | void* get_PS_to_PL_dma_instructions_pointer(uint32_t offset); // should only be written to 47 | 48 | uint32_t wait_for_update(); // wait for new data from the FPGA to be ready 49 | 50 | uint32_t set_update_frequency(uint32_t frequency); // frequency at which the FPGA will update in Hz 51 | 52 | void cache_flush_all(); // writes any changed data from CPU to memory 53 | void cache_invalidate_all(); // invalidates cached memory from FPGA 54 | 55 | 56 | 57 | private: 58 | fpga_mem_layout mem_layout; 59 | 60 | uint16_t* fpga_main_trigger_counter = nullptr; // 16 bit counter at 25 Mhz 61 | bool first_cycle = true; 62 | 63 | void* ocm_base_pointer; 64 | 65 | struct pollfd mem_update_running_fds[1]; 66 | struct pollfd mem_update_done_fds[1]; 67 | 68 | void cache_flush(void* addr, uint32_t size); 69 | void cache_invalidate(void* addr, uint32_t size); 70 | 71 | void error_if_nullptr(); // check to ensure memory is allocated before returning any pointers 72 | }; 73 | -------------------------------------------------------------------------------- /controller-software/core/controller/inc/fpga_module_driver_factory.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "fpga_instructions.h" 12 | 13 | using json = nlohmann::json; 14 | 15 | // base driver for which all other module drivers will inherit from 16 | class base_driver { 17 | public: 18 | const uint32_t* microseconds = nullptr; // pointer to the microseconds counter in the fpga manager, used for timing 19 | 20 | virtual uint32_t load_config(json config, std::vector* instructions) = 0; // configures the internal memory pointers 21 | //virtual uint32_t get_base_instructions(std::vector* instructions) = 0; // gets the base instructions for the driver, just for syncing required memory with the PS 22 | virtual uint32_t run() = 0; // run the driver, called after each FPGA update 23 | 24 | fpga_mem base_mem; // this is what is required for MINIMUM functionality of a driver 25 | 26 | 27 | // TESTING ONLY // 28 | int32_t* cmd_q_current_milliamps = nullptr; // for testing only, this should be removed 29 | uint32_t* encoder_pos = nullptr; // for testing only, this should be removed 30 | uint32_t* encoder_multiturn_count = nullptr; // for testing only, this should be removed 31 | 32 | protected: 33 | 34 | // helper function for loading values from the config file 35 | // WARNING: this may cause issues if you try to load a value as a different type than it is stored as 36 | template 37 | bool load_json_value(const json& config, const std::string& value_name, T* dest); 38 | 39 | uint8_t node_address = 255; // address of the node in the FPGA 40 | }; 41 | 42 | 43 | template 44 | bool base_driver::load_json_value(const json& config, const std::string& value_name, T* dest){ 45 | // helper function for loading values from the config file 46 | if (!config.contains(value_name)) { 47 | std::cerr << "Error: Constant '" << value_name << "' not found in config." << std::endl; 48 | return false; 49 | } 50 | 51 | *dest = config[value_name].get(); 52 | return true; 53 | } 54 | 55 | 56 | 57 | 58 | 59 | 60 | // Factory class for creating drivers 61 | class Driver_Factory { 62 | public: 63 | using Creator = std::function()>; 64 | 65 | // Registers a creator function with a string key 66 | static void registerType(const std::string& typeName, Creator creator) { 67 | auto& map = getMap(); 68 | if (map.find(typeName) != map.end()) { 69 | throw std::runtime_error("Type already registered: " + typeName); 70 | } 71 | map[typeName] = creator; 72 | } 73 | 74 | // Creates an object based on the string key 75 | static std::shared_ptr create_shared(const std::string& typeName) { 76 | auto& map = getMap(); 77 | auto it = map.find(typeName); 78 | if (it != map.end()) { 79 | return (it->second)(); 80 | } 81 | throw std::runtime_error("Type not registered: " + typeName); 82 | } 83 | 84 | private: 85 | // Returns the static map 86 | static std::unordered_map& getMap() { 87 | static std::unordered_map map; 88 | return map; 89 | } 90 | }; 91 | 92 | // Template class to register a type with the factory 93 | template 94 | class Driver_Registrar { 95 | public: 96 | Driver_Registrar(const std::string& typeName) { 97 | Driver_Factory::registerType(typeName, []() -> std::shared_ptr { 98 | return std::make_shared(); 99 | }); 100 | 101 | std::cout << "Creating driver: " << typeName << std::endl; 102 | } 103 | }; 104 | -------------------------------------------------------------------------------- /controller-software/core/controller/inc/fpga_module_manager.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "json.hpp" 5 | #include "fpga_interface.h" 6 | 7 | 8 | // include all drivers here 9 | 10 | #include "serial_interface_card.h" 11 | #include "global_timers.h" 12 | #include "fanuc_encoders.h" 13 | #include "em_serial_controller.h" 14 | 15 | 16 | #pragma once 17 | using json = nlohmann::json; 18 | 19 | 20 | class fpga_module_manager { 21 | public: 22 | fpga_module_manager(); 23 | ~fpga_module_manager(); 24 | 25 | uint32_t load_config(std::string config_file); 26 | 27 | uint32_t set_fpga_interface(Fpga_Interface* fpga_interface); 28 | 29 | uint32_t initialize_fpga(); 30 | 31 | uint32_t load_drivers(); 32 | 33 | uint32_t create_global_variables(); 34 | 35 | uint32_t run_update(); 36 | 37 | std::shared_ptr get_driver(uint32_t index); 38 | 39 | private: 40 | 41 | uint32_t microseconds = 0; 42 | 43 | json config; 44 | Fpga_Interface* fpga_interface; 45 | fpga_mem_layout mem_layout; 46 | 47 | void* PS_PL_control_ptr = nullptr; 48 | void* PL_PS_control_ptr = nullptr; 49 | void* PS_PL_data_ptr = nullptr; 50 | void* PL_PS_data_ptr = nullptr; 51 | void* PS_PL_dma_instructions_ptr = nullptr; 52 | 53 | // how much memory has been allocated to the drivers 54 | uint32_t allocated_PS_PL_address = 0; 55 | uint32_t allocated_PL_PS_address = 0; 56 | 57 | std::vector fpga_instructions; 58 | bool instructions_modified = false; 59 | 60 | std::vector> drivers; // this will point to all drivers that are loaded 61 | 62 | uint32_t load_driver(json module_config); 63 | 64 | template 65 | bool load_json_value(const json& config, const std::string& value_name, T* dest); 66 | 67 | uint32_t load_mem_layout(); 68 | 69 | uint32_t set_memory_pointers(fpga_mem* mem); // set the memory pointers for the driver 70 | 71 | uint32_t allocate_driver_memory(const fpga_mem* mem); // allocate memory that the driver will use 72 | 73 | uint32_t write_instructions_to_fpga(); // write the instructions to the FPGA 74 | 75 | }; -------------------------------------------------------------------------------- /controller-software/core/controller/nodes/base_node.h: -------------------------------------------------------------------------------- 1 | #include "../json.hpp" 2 | #include 3 | #include "node_io.h" 4 | 5 | #pragma once 6 | 7 | using json = nlohmann::json; 8 | 9 | class base_node{ 10 | protected: 11 | std::map inputs; 12 | std::map outputs; 13 | 14 | bool marked_for_deletion = false; 15 | 16 | public: 17 | 18 | int execution_number = -256; 19 | 20 | base_node(){ 21 | // do nothing 22 | } 23 | 24 | unsigned int get_output_object(std::string output_name, output*& ptr){ 25 | if(outputs.find(output_name) == outputs.end()){ 26 | return 1; // output name not found 27 | } 28 | ptr = &outputs[output_name]; 29 | return 0; 30 | } 31 | 32 | unsigned int connect_input(std::string input_name, std::shared_ptr source_node, std::string source_output_name){ 33 | if(inputs.find(input_name) == inputs.end()){ 34 | return 1; // internal input name not found 35 | } 36 | 37 | output* output_pointer = nullptr; 38 | if(source_node->get_output_object(source_output_name, output_pointer) != 0){ 39 | return 2; // source output name not found 40 | } 41 | 42 | if (inputs[input_name].set_input_data(output_pointer) != 0){ 43 | return 3; // error setting input data 44 | } 45 | 46 | return 0; 47 | } 48 | 49 | unsigned int connect_input(std::string input_name, output* source_output){ 50 | if(inputs.find(input_name) == inputs.end()){ 51 | return 1; // internal input name not found 52 | } 53 | 54 | if (inputs[input_name].set_input_data(source_output) != 0){ 55 | return 2; // error setting input data 56 | } 57 | 58 | return 0; 59 | } 60 | 61 | unsigned int disconnect_input(std::string input_name){ 62 | if(inputs.find(input_name) == inputs.end()){ 63 | return 1; // internal input name not found 64 | } 65 | 66 | if (inputs[input_name].set_default() != 0){ 67 | return 2; // error setting default value 68 | } 69 | 70 | return 0; 71 | } 72 | 73 | // fix any inputs pointing to invalid ouitputs by switching to the default value 74 | unsigned int reconnect_inputs(){ 75 | for (auto& pair : inputs) { 76 | pair.second.reconnect(); 77 | } 78 | return 0; 79 | } 80 | 81 | unsigned int configure_execution_number(bool* execution_number_modified){ 82 | if(reconnect_inputs() != 0){ 83 | return 1; // error reconnecting inputs 84 | } 85 | 86 | // find the highest execution number of all inputs 87 | int highest_execution_input = -256; 88 | for (auto& pair : inputs) { 89 | if(*pair.second.source_execution_number > highest_execution_input){ 90 | highest_execution_input = *pair.second.source_execution_number; 91 | } 92 | } 93 | 94 | // update the execution number if needed and signal it was modified 95 | if(execution_number != highest_execution_input + 1){ 96 | *execution_number_modified = true; 97 | execution_number = highest_execution_input + 1; 98 | } 99 | 100 | return 0; 101 | } 102 | 103 | // mark all outputs for deletion 104 | void mark_for_deletion(){ 105 | for (auto& pair : outputs) { 106 | pair.second.mark_for_deletion(); 107 | } 108 | marked_for_deletion = true; 109 | } 110 | 111 | // run node 112 | virtual unsigned int run() = 0; 113 | 114 | // configure internal settings 115 | virtual unsigned int configure_settings(json* data){ 116 | return 1; // no settings to configure 117 | } 118 | 119 | ~base_node(){ 120 | // do nothing 121 | } 122 | }; -------------------------------------------------------------------------------- /controller-software/core/controller/nodes/generic_nodes/bool_constant.h: -------------------------------------------------------------------------------- 1 | #include "../node_factory.h" 2 | 3 | 4 | class bool_constant: public base_node { 5 | private: 6 | bool output_value = false; 7 | 8 | public: 9 | 10 | bool_constant(){ 11 | execution_number = -1; 12 | 13 | outputs.emplace("output", output(io_type::BOOL, &output_value, &execution_number)); 14 | } 15 | 16 | unsigned int run() override { 17 | return 0; 18 | } 19 | 20 | }; 21 | 22 | static Driver_Registrar node_registrar_bool_constant("bool_constant"); -------------------------------------------------------------------------------- /controller-software/core/controller/nodes/generic_nodes/bool_print_cout.h: -------------------------------------------------------------------------------- 1 | #include "../base_node.h" 2 | #include 3 | 4 | class bool_print_cout: public base_node { 5 | private: 6 | bool default_input_value = false; 7 | bool output_value = true; 8 | 9 | input* input_value = nullptr; 10 | public: 11 | 12 | bool_print_cout(){ 13 | inputs.emplace("input", input(io_type::BOOL, &default_input_value)); 14 | 15 | input_value = &inputs["input"]; 16 | } 17 | 18 | unsigned int run() override { 19 | output_value = (*reinterpret_cast(input_value->data_pointer)); 20 | std::cout << "bool output: " << output_value << std::endl; 21 | return 0; 22 | } 23 | 24 | }; -------------------------------------------------------------------------------- /controller-software/core/controller/nodes/generic_nodes/logic_and.h: -------------------------------------------------------------------------------- 1 | #include "../base_node.h" 2 | 3 | class logic_and: public base_node { 4 | private: 5 | bool default_input_value = false; 6 | bool output_value = true; 7 | 8 | input* input_value_A = nullptr; 9 | input* input_value_B = nullptr; 10 | public: 11 | 12 | logic_and(){ 13 | inputs.emplace("input_A", input(io_type::BOOL, &default_input_value)); 14 | inputs.emplace("input_B", input(io_type::BOOL, &default_input_value)); 15 | 16 | outputs.emplace("output", output(io_type::BOOL, &output_value, &execution_number)); 17 | 18 | input_value_A = &inputs["input_A"]; 19 | input_value_B = &inputs["input_B"]; 20 | } 21 | 22 | unsigned int run() override { 23 | output_value = (*reinterpret_cast(input_value_A->data_pointer)) & (*reinterpret_cast(input_value_B->data_pointer)); 24 | return 0; 25 | } 26 | 27 | }; -------------------------------------------------------------------------------- /controller-software/core/controller/nodes/generic_nodes/logic_not.h: -------------------------------------------------------------------------------- 1 | #include "../base_node.h" 2 | 3 | class logic_not: public base_node { 4 | private: 5 | bool default_input_value = false; 6 | bool output_value = true; 7 | 8 | input* input_value = nullptr; 9 | public: 10 | 11 | logic_not(){ 12 | inputs.emplace("input", input(io_type::BOOL, &default_input_value)); 13 | 14 | outputs.emplace("output", output(io_type::BOOL, &output_value, &execution_number)); 15 | 16 | input_value = &inputs["input"]; 17 | } 18 | 19 | unsigned int run() override { 20 | output_value = !(*reinterpret_cast(input_value->data_pointer)); 21 | return 0; 22 | } 23 | 24 | }; -------------------------------------------------------------------------------- /controller-software/core/controller/nodes/generic_nodes/nothing_delay.h: -------------------------------------------------------------------------------- 1 | #include "../base_node.h" 2 | #include 3 | #include 4 | 5 | class nothing_delay: public base_node { 6 | private: 7 | bool default_input_value = false; 8 | bool output_value = false; 9 | 10 | input* input_value = nullptr; 11 | public: 12 | 13 | nothing_delay(){ 14 | inputs.emplace("input", input(io_type::BOOL, &default_input_value)); 15 | 16 | outputs.emplace("output", output(io_type::BOOL, &output_value, &execution_number)); 17 | 18 | input_value = &inputs["input"]; 19 | } 20 | 21 | unsigned int run() override { 22 | std::this_thread::sleep_for(std::chrono::seconds(1)); 23 | output_value = (*reinterpret_cast(input_value->data_pointer)); 24 | return 0; 25 | } 26 | 27 | }; -------------------------------------------------------------------------------- /controller-software/core/controller/nodes/global_variables/base_global_variable.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../node_io.h" 4 | #include 5 | 6 | // global variables are accessible by all node networks 7 | // values do not persist between restarts 8 | 9 | 10 | #include 11 | 12 | class global_variable { 13 | private: 14 | io_type var_type = io_type::UNDEFINED; 15 | std::shared_ptr data_pointer = nullptr; 16 | //std::string name; 17 | 18 | public: 19 | 20 | global_variable(io_type type_){ 21 | //name = name_; 22 | var_type = type_; 23 | 24 | switch (var_type) 25 | { 26 | case io_type::UINT32: 27 | data_pointer = std::make_shared(0); 28 | break; 29 | case io_type::INT32: 30 | data_pointer = std::make_shared(0); 31 | break; 32 | case io_type::DOUBLE: 33 | data_pointer = std::make_shared(0.0); 34 | break; 35 | case io_type::BOOL: 36 | data_pointer = std::make_shared(false); 37 | break; 38 | default: 39 | break; // TODO: add error handling 40 | } 41 | } 42 | 43 | // uint32_t set_name(std::string name_){ 44 | // name = name_; 45 | // return 0; 46 | // } 47 | 48 | std::shared_ptr get_data_pointer(){ 49 | return data_pointer; 50 | } 51 | 52 | bool is_in_use(){ 53 | if(data_pointer.use_count() > 1){ 54 | return true; 55 | } 56 | return false; 57 | } 58 | 59 | ~global_variable(){ 60 | if(is_in_use()){ 61 | std::cerr << "Error: global variable was still in use when deleted." << std::endl; 62 | } 63 | } 64 | };; 65 | -------------------------------------------------------------------------------- /controller-software/core/controller/nodes/node_factory.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "base_node.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class Node_Factory { 11 | public: 12 | using Creator = std::function()>; 13 | 14 | // Registers a creator function with a string key 15 | static void registerType(const std::string& typeName, Creator creator) { 16 | auto& map = getMap(); 17 | if (map.find(typeName) != map.end()) { 18 | throw std::runtime_error("Type already registered: " + typeName); 19 | } 20 | map[typeName] = creator; 21 | } 22 | 23 | // Creates an object based on the string key 24 | static std::shared_ptr create_shared(const std::string& typeName) { 25 | auto& map = getMap(); 26 | auto it = map.find(typeName); 27 | if (it != map.end()) { 28 | return (it->second)(); 29 | } 30 | throw std::runtime_error("Type not registered: " + typeName); 31 | } 32 | 33 | private: 34 | // Returns the static map 35 | static std::unordered_map& getMap() { 36 | static std::unordered_map map; 37 | return map; 38 | } 39 | }; 40 | 41 | // Template class to register a type with the factory 42 | template 43 | class Driver_Registrar { 44 | public: 45 | Driver_Registrar(const std::string& typeName) { 46 | Node_Factory::registerType(typeName, []() -> std::shared_ptr { 47 | return std::make_shared(); 48 | }); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /controller-software/core/controller/nodes/node_io.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | #pragma once 5 | 6 | enum io_type{ 7 | UNDEFINED, 8 | UINT32, 9 | INT32, 10 | DOUBLE, 11 | BOOL 12 | }; 13 | 14 | class output{ 15 | public: 16 | io_type type = UNDEFINED; 17 | 18 | int* source_execution_number = nullptr; 19 | 20 | bool delete_flag = false; 21 | 22 | void* data_pointer = nullptr; 23 | 24 | output() = default; 25 | 26 | output(io_type type_, void* data, int* source_execution_number_){ 27 | type = type_; 28 | data_pointer = data; 29 | source_execution_number = source_execution_number_; 30 | } 31 | 32 | void mark_for_deletion(){ 33 | delete_flag = true; 34 | } 35 | 36 | ~output(){ 37 | // do nothing 38 | } 39 | }; 40 | 41 | class input{ 42 | private: 43 | io_type type = UNDEFINED; 44 | 45 | int default_execution_number = -256; 46 | 47 | void* default_value = nullptr; 48 | 49 | output* source_output = nullptr; 50 | 51 | 52 | 53 | public: 54 | 55 | int* source_execution_number = nullptr; 56 | 57 | void* data_pointer = nullptr; 58 | 59 | input() = default; 60 | 61 | input(io_type type_, void* default_value_){ 62 | type = type_; 63 | default_value = default_value_; 64 | } 65 | 66 | unsigned int set_default(){ 67 | if(default_value == nullptr){ 68 | std::cerr << "Error: default value is null" << std::endl; 69 | return 1; // default value is null 70 | } 71 | source_execution_number = &default_execution_number; 72 | data_pointer = default_value; 73 | source_output = nullptr; 74 | return 0; 75 | } 76 | 77 | // set where the input should retreive its data from 78 | unsigned int set_input_data(output* source){ 79 | unsigned int ret = 0; 80 | if(source == nullptr){ 81 | ret = 1; // source is null 82 | if(set_default()){ 83 | ret = 6; // error setting input source and setting default value 84 | } 85 | } 86 | 87 | else if(source->type != type){ 88 | ret = 2; // type mismatch 89 | } 90 | 91 | else if(source->data_pointer == nullptr){ 92 | ret = 3; // data pointer is null 93 | } 94 | 95 | else if(source->delete_flag){ 96 | ret = 4; // source output is marked for deletion 97 | } 98 | 99 | else if(source->source_execution_number == nullptr){ 100 | ret = 5; // source execution number is null 101 | } 102 | 103 | if(ret){ 104 | return ret; 105 | } 106 | 107 | else{ 108 | source_output = source; 109 | data_pointer = source->data_pointer; 110 | source_execution_number = source->source_execution_number; 111 | } 112 | return 0; 113 | } 114 | 115 | unsigned int reconnect(){ 116 | return set_input_data(source_output); 117 | } 118 | 119 | ~input(){ 120 | // do nothing 121 | } 122 | }; -------------------------------------------------------------------------------- /controller-software/core/controller/src/readme.md: -------------------------------------------------------------------------------- 1 | install cross-platform compilers: sudo apt install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf 2 | 3 | install build tools: 4 | sudo apt update 5 | sudo apt install build-essential ninja-build 6 | 7 | install debugger: 8 | sudo apt install gdb-multiarch 9 | 10 | create ssh keys: 11 | ssh-keygen -t rsa -b 4096 -C "your_email@example.com" 12 | save them in the core/ssh folder 13 | 14 | copy ssh key to zynq: 15 | ssh-copy-id -i .ssh/zynq em-os@192.168.1.238 16 | 17 | (remove past configs if zynq has been re-imaged) 18 | rm -r /home/excessive/.ssh 19 | 20 | create ssh config: 21 | mkdir -p ~/.ssh && chmod 700 ~/.ssh 22 | 23 | create config: 24 | touch ~/.ssh/config 25 | 26 | set permissions: 27 | chmod 600 ~/.ssh/config 28 | 29 | configure ssh settings: 30 | nano ~/.ssh/config 31 | 32 | add config: 33 | Host zynq 34 | HostName 192.168.1.238 35 | User em-os 36 | IdentityFile /home/excessive/controller-software/controller-software/core/.ssh/zynq 37 | Port 22 38 | 39 | build project to copy files to zynq 40 | 41 | TODO: figure out a way to better handle the permissions, currently you must enter the password each time (maybe not that bad?) 42 | 43 | allow write access so we can update it later from vs code (note this is unsafe since a malicious bin could be added) 44 | sudo chmod u+w controller/bin/controller_core 45 | 46 | 47 | 48 | 49 | petalinux setup: 50 | package images: 51 | petalinux-package --boot --fsbl --u-boot --force 52 | 53 | create disk image: 54 | petalinux-package --wic --outdir /home/excessive --wic-extra-args "-c xz" -b "BOOT.BIN,image.ub,boot.scr" --wks project-spec/configs/rootfs.wks 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /controller-software/core/devices/em_hvsd/config.yaml: -------------------------------------------------------------------------------- 1 | gui_node_config: 2 | common: 3 | params: 4 | serial_address: 5 | name: "Serial address" 6 | type: "int" 7 | unit: "" 8 | required: true 9 | min: 0 10 | max: 255 11 | description: "Address of the device on the serial bus" 12 | 13 | hard_current_limit: 14 | name: "Hard current limit" 15 | type: "float" 16 | unit: "Amps" 17 | required: true 18 | min: 0 19 | max: 60 20 | description: "Maximum current that the device can output in percent of the maximum current" 21 | auto_fill_enable: true 22 | auto_fill_by: ["hard_max_current"] 23 | 24 | connectors: 25 | #TODO: Add connectors 26 | 27 | mode_specific: 28 | pmsm_foc: 29 | params: 30 | commutation_offset: 31 | name: "Commutation offset" 32 | type: "float" 33 | unit: "Radians" 34 | required: false 35 | min: -6.28 36 | max: 6.28 37 | description: "Offset of the commutation angle in radians" 38 | auto_fill_enable: true 39 | auto_fill_by: [] 40 | 41 | connectors: 42 | #TODO: Add connectors -------------------------------------------------------------------------------- /controller-software/core/zynq_files/controller/bin/controller_core: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExcessiveMotion/controller-software/0503bb292ecb54b9c41ecbcdc22a93bbeb4c36b0/controller-software/core/zynq_files/controller/bin/controller_core -------------------------------------------------------------------------------- /controller-software/core/zynq_files/controller/config/fpga_configs/bit_files/bitfile.bit.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExcessiveMotion/controller-software/0503bb292ecb54b9c41ecbcdc22a93bbeb4c36b0/controller-software/core/zynq_files/controller/config/fpga_configs/bit_files/bitfile.bit.bin -------------------------------------------------------------------------------- /controller-software/web/api: -------------------------------------------------------------------------------- 1 | { 2 | "openrpc": "1.2.6", 3 | "info": { 4 | "title": "Controller API", 5 | "version": "1.0.0", 6 | "description": "API for interfacing with the controller" 7 | }, 8 | "methods": [ 9 | { 10 | "name": "move", 11 | "description": "Moves the robot to a specified position", 12 | "params": [ 13 | { 14 | "name": "x", 15 | "schema": { 16 | "type": "number" 17 | }, 18 | "description": "The x-coordinate" 19 | }, 20 | { 21 | "name": "y", 22 | "schema": { 23 | "type": "number" 24 | }, 25 | "description": "The y-coordinate" 26 | }, 27 | { 28 | "name": "z", 29 | "schema": { 30 | "type": "number" 31 | }, 32 | "description": "The z-coordinate" 33 | } 34 | ], 35 | "result": { 36 | "name": "success", 37 | "schema": { 38 | "type": "boolean" 39 | }, 40 | "description": "Indicates if the move was successful" 41 | } 42 | }, 43 | { 44 | "name": "rotate", 45 | "description": "Rotates the robot to a specified angle", 46 | "params": [ 47 | { 48 | "name": "angle", 49 | "schema": { 50 | "type": "number" 51 | }, 52 | "description": "The angle to rotate to in degrees" 53 | } 54 | ], 55 | "result": { 56 | "name": "success", 57 | "schema": { 58 | "type": "boolean" 59 | }, 60 | "description": "Indicates if the rotation was successful" 61 | } 62 | }, 63 | { 64 | "name": "getStatus", 65 | "description": "Gets the current status of the robot", 66 | "params": [], 67 | "result": { 68 | "name": "status", 69 | "schema": { 70 | "type": "string" 71 | }, 72 | "description": "The current status of the robot" 73 | } 74 | } 75 | ] 76 | } -------------------------------------------------------------------------------- /controller-software/web/api_helper.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace v8; 6 | 7 | class API_Helper { 8 | public: 9 | void set_args(const FunctionCallbackInfo& args){ 10 | isolate = args.GetIsolate(); 11 | } 12 | 13 | void check_arg_count(int count){ 14 | if (args.Length() < count) { 15 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Missing argument(s)").ToLocalChecked())); 16 | throw std::invalid_argument("Missing argument(s)"); 17 | } 18 | } 19 | 20 | std::string get_string_arg(int index){ 21 | check_arg_count(index); 22 | 23 | if (!args[index]->IsString()) { 24 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Could not parse argument as string").ToLocalChecked())); 25 | throw std::invalid_argument("Could not parse argument as string"); 26 | } 27 | String::Utf8Value utf8_value(isolate, args[index]); 28 | return std::string(*utf8_value); 29 | } 30 | 31 | Local get_object_arg(int index){ 32 | check_arg_count(index); 33 | // Check if the first argument is an object 34 | if (!args[index]->IsObject()) { 35 | isolate->ThrowException(Exception::TypeError( 36 | String::NewFromUtf8(isolate, "Expected an object").ToLocalChecked())); 37 | throw std::invalid_argument("Expected an object"); 38 | } 39 | 40 | // Convert the first argument to a V8 Object 41 | Local obj = args[index]->ToObject(isolate->GetCurrentContext()).ToLocalChecked(); 42 | return obj; 43 | } 44 | 45 | private: 46 | Isolate* isolate; 47 | 48 | 49 | }; -------------------------------------------------------------------------------- /controller-software/web/api_test.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import websockets 3 | import json 4 | import ssl 5 | 6 | # Global variables 7 | request_id = 1 8 | login_request_id = None 9 | 10 | def create_rpc_request(method, params=[]): 11 | """Utility function to create JSON-RPC 2.0 requests.""" 12 | global request_id 13 | req = { 14 | 'jsonrpc': '2.0', 15 | 'id': request_id, 16 | 'method': method, 17 | 'params': params 18 | } 19 | request_id += 1 20 | return req 21 | 22 | def handle_response(response): 23 | """Handle responses from the WebSocket server.""" 24 | print("Received response:", response) 25 | if 'error' in response: 26 | print(f"Error {response['error']['code']}: {response['error']['message']}") 27 | else: 28 | # Handle login response 29 | if response['id'] == login_request_id: 30 | if response['result'] and response['result']['success']: 31 | print("Login successful.") 32 | else: 33 | print("Login failed.") 34 | else: 35 | # Handle other responses 36 | print("Received result:", response['result']) 37 | 38 | def get_credentials(): 39 | """Prompt the user for username and password.""" 40 | return 'admin', 'password' 41 | username = input('Enter username: ') 42 | password = input('Enter password: ') 43 | return username, password 44 | 45 | async def handle_login(ws): 46 | """Handle user login.""" 47 | global login_request_id 48 | username, password = get_credentials() 49 | login_request = create_rpc_request('Login', [username, password]) 50 | login_request_id = login_request['id'] 51 | await ws.send(json.dumps(login_request)) 52 | print(f'Sent login request with id {login_request_id}') 53 | # Wait for response 54 | response = await ws.recv() 55 | response = json.loads(response) 56 | handle_response(response) 57 | 58 | async def send_print_uint32(ws, value: int = 0): 59 | """Send a print_uint32 request.""" 60 | print_request = create_rpc_request('print_uint32', [value]) 61 | await ws.send(json.dumps(print_request)) 62 | print(f'Sent print_uint32 request with id {print_request["id"]}') 63 | # Wait for response 64 | response = await ws.recv() 65 | response = json.loads(response) 66 | handle_response(response) 67 | 68 | async def handle_logout(ws): 69 | """Handle user logout.""" 70 | logout_request = create_rpc_request('Logout') 71 | await ws.send(json.dumps(logout_request)) 72 | print(f'Sent logout request with id {logout_request["id"]}') 73 | # Wait for response 74 | response = await ws.recv() 75 | response = json.loads(response) 76 | handle_response(response) 77 | 78 | async def main(): 79 | """Main function to connect to the WebSocket and perform actions.""" 80 | uri = 'wss://localhost:443' 81 | try: 82 | ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) 83 | ssl_context.check_hostname = False 84 | ssl_context.verify_mode = ssl.CERT_NONE 85 | async with websockets.connect(uri, ssl=ssl_context) as ws: 86 | print('WebSocket connection opened') 87 | await handle_login(ws) 88 | await send_print_uint32(ws, 1234) 89 | await handle_logout(ws) 90 | print('WebSocket connection closed') 91 | except Exception as e: 92 | print(f"An error occurred: {e}") 93 | 94 | if __name__ == '__main__': 95 | asyncio.run(main()) 96 | -------------------------------------------------------------------------------- /controller-software/web/binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "controller_API", 5 | "sources": [ "controller_interface.cpp"], 6 | "include_dirs": [ "../core/api_drivers" ], 7 | "libraries": [ 8 | "-lrt" 9 | ] 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /controller-software/web/controller_interface.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "../core/api_drivers/web_api.h" 3 | #include "../core/json.hpp" 4 | 5 | using namespace v8; 6 | using json = nlohmann::json; 7 | 8 | web_api api; 9 | 10 | void get_responses_from_controller(const FunctionCallbackInfo& args){ 11 | Isolate* isolate = args.GetIsolate(); 12 | json response; 13 | 14 | unsigned int ret = api.get_completed_calls(&response); 15 | 16 | if(ret != 0){ 17 | isolate->ThrowException(Exception::TypeError( 18 | String::NewFromUtf8(isolate, "Error getting completed calls").ToLocalChecked())); 19 | } 20 | 21 | // Convert the nlohmann::json object to a string 22 | std::string jsonString = response.dump(); 23 | 24 | // Use V8's JSON::Parse to convert the JSON string into a V8 object 25 | Local v8String = String::NewFromUtf8(isolate, jsonString.c_str()).ToLocalChecked(); 26 | Local context = isolate->GetCurrentContext(); 27 | Local v8JsonObject; 28 | if (!JSON::Parse(context, v8String).ToLocal(&v8JsonObject)) { 29 | isolate->ThrowException(Exception::SyntaxError( 30 | String::NewFromUtf8(isolate, "Failed to parse JSON").ToLocalChecked())); 31 | return; 32 | } 33 | 34 | // Return the V8 JSON object to Node.js 35 | args.GetReturnValue().Set(v8JsonObject); 36 | 37 | return; 38 | } 39 | 40 | void send_data_to_controller(const FunctionCallbackInfo& args){ 41 | Isolate* isolate = args.GetIsolate(); 42 | 43 | unsigned int ret = api.write_web_calls_to_controller(); 44 | 45 | if(ret != 0){ 46 | isolate->ThrowException(Exception::TypeError( 47 | String::NewFromUtf8(isolate, "Failed to write data to controller").ToLocalChecked())); 48 | return; 49 | } 50 | 51 | return; 52 | } 53 | 54 | void api_call(const FunctionCallbackInfo& args) { 55 | Isolate* isolate = args.GetIsolate(); 56 | 57 | // Variable to hold the resulting string 58 | std::string jsonString; 59 | 60 | // Check if the first argument is a string 61 | if (args[0]->IsString()) { 62 | // Convert the V8 string to a C++ std::string 63 | String::Utf8Value utf8(isolate, args[0]); 64 | jsonString = std::string(*utf8); 65 | } 66 | // Check if the first argument is an object 67 | else if (args[0]->IsObject()) { 68 | Local context = isolate->GetCurrentContext(); 69 | Local obj = args[0]->ToObject(context).ToLocalChecked(); 70 | 71 | // Use JSON::Stringify to convert the object to a JSON string 72 | Local jsonStr; 73 | Local jsonObj = JSON::Stringify(context, obj).ToLocalChecked(); 74 | 75 | // Convert the V8 JSON string to a C++ std::string 76 | String::Utf8Value utf8Json(isolate, jsonObj); 77 | jsonString = std::string(*utf8Json); 78 | } 79 | else { 80 | // If the argument is neither a string nor an object, throw an error 81 | isolate->ThrowException(Exception::TypeError(String::NewFromUtf8(isolate, "Argument must be a string or object").ToLocalChecked())); 82 | return; 83 | } 84 | 85 | json parsedJson; 86 | parsedJson = json::parse(jsonString); 87 | 88 | unsigned int ret = 0; 89 | unsigned int call_number = 0; 90 | 91 | ret = api.add_call(&parsedJson, &call_number); 92 | 93 | if(ret != 0){ 94 | isolate->ThrowException(Exception::TypeError( 95 | String::NewFromUtf8(isolate, "Failed to add call").ToLocalChecked())); 96 | return; 97 | } 98 | 99 | args.GetReturnValue().Set(Number::New(isolate, call_number)); 100 | 101 | return; 102 | } 103 | 104 | void Initialize(Local exports) { 105 | NODE_SET_METHOD(exports, "api_call", api_call); 106 | NODE_SET_METHOD(exports, "send_data", send_data_to_controller); 107 | NODE_SET_METHOD(exports, "get_responses", get_responses_from_controller); 108 | } 109 | 110 | NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) -------------------------------------------------------------------------------- /controller-software/web/openrpc-methods.js: -------------------------------------------------------------------------------- 1 | // openrpc-methods.js 2 | 3 | module.exports = { 4 | // 'Machine_on': (params, userSession) => { 5 | // // Logic for turning the machine on 6 | // console.log(`Machine turned on by ${userSession.username}`); 7 | // return { success: true, message: 'Machine is now on.' }; 8 | // }, 9 | // 'Machine_off': (params, userSession) => { 10 | // // Logic for turning the machine off 11 | // console.log(`Machine turned off by ${userSession.username}`); 12 | // return { success: true, message: 'Machine is now off.' }; 13 | // }, 14 | 'print_uint32': (params, userSession) => { 15 | // Logic for printing a uint32 value 16 | console.log(`Printing uint32 value ${params[0]} by ${userSession.username}`); 17 | return { 18 | immediate: false, 19 | call_data: {call_name: "print_uint32", value: params[0]}, 20 | }; 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /controller-software/web/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "1.0.0", 4 | "main": "main.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "", 9 | "license": "ISC", 10 | "description": "", 11 | "dependencies": { 12 | "@open-rpc/client-js": "^1.8.1", 13 | "@open-rpc/server-js": "^1.9.5", 14 | "api": "file:", 15 | "crypto": "^1.0.1", 16 | "express": "^4.21.0", 17 | "litegraph.js": "^0.7.18", 18 | "selfsigned": "^2.4.1", 19 | "ws": "^8.18.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /controller-software/web/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebSocket SPA 7 | 15 | 16 | 17 |
18 |

Login

19 | 20 | 21 | 22 |
23 |
24 |

Home

25 |

Welcome to the home page!

26 | 27 | \ 28 | 29 |
30 | 31 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /controller-software/web/users.json: -------------------------------------------------------------------------------- 1 | { 2 | "admin": { 3 | "password": "password", 4 | "allowedFunctions": ["Machine_on", "Machine_off", "print_uint32", "Logout"] 5 | }, 6 | "user1": { 7 | "password": "user1pass", 8 | "allowedFunctions": ["Machine_on", "Logout"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /em-os/.gitignore: -------------------------------------------------------------------------------- 1 | */*/config.old 2 | */*/rootfs_config.old 3 | build/ 4 | images/linux/ 5 | pre-built/linux/ 6 | .petalinux/* 7 | !.petalinux/metadata 8 | *.o 9 | *.jou 10 | *.log 11 | *.xil 12 | /components/plnx_workspace 13 | /components/yocto 14 | -------------------------------------------------------------------------------- /em-os/build_pack_image.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # build 4 | petalinux-build 5 | 6 | # package binary files 7 | petalinux-package --boot --fsbl --u-boot --force 8 | 9 | # create image 10 | petalinux-package --wic --outdir ./images/linux --wic-extra-args "-c xz" -b "BOOT.BIN,image.ub,boot.scr" --wks project-spec/configs/rootfs.wks 11 | -------------------------------------------------------------------------------- /em-os/build_run_qemu.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # build 4 | petalinux-build 5 | 6 | # package binary files 7 | petalinux-package --boot --fsbl --u-boot --force 8 | 9 | # create image 10 | petalinux-package --wic --outdir ./images/linux -b "BOOT.BIN,image.ub,boot.scr" --wks project-spec/configs/rootfs.wks 11 | 12 | # run emulator 13 | petalinux-boot --qemu --kernel 14 | 15 | -------------------------------------------------------------------------------- /em-os/project-spec/attributes: -------------------------------------------------------------------------------- 1 | #Virtual Providers 2 | 3 | 4 | 5 | #defconfigs 6 | 7 | UBOOT_DEFAULT_DEFCONFIG="xilinx_zynq_virt_defconfig" 8 | -------------------------------------------------------------------------------- /em-os/project-spec/configs/.statistics: -------------------------------------------------------------------------------- 1 | USER_RFS_CFG=67b1c6cb8b8ea6663ae2e85a80b31b829f449112730c232c77ac1606bf4122ec 2 | RFS_CONF=bd06e34db12daa43a1f827642713586e5cb0283ed004ab51c8072c0898c53b3f 3 | HW_FILE=74619627164c2cb1539a62748a99d3098f65d88aa8c61597f0f0a72f1281736f 4 | SYSTEM_CONF=1c20b0b9962cd5c621a5a6f399b649b8ea8e4a566953f9eadde08e54832be83c 5 | -------------------------------------------------------------------------------- /em-os/project-spec/configs/busybox/inetd.conf: -------------------------------------------------------------------------------- 1 | #/etc/inetd.conf: see inetd(8) for further informations. 2 | # 3 | # Internet server configuration database 4 | # 5 | # If you want to disable an entry so it isn't touched during 6 | # package updates just comment it out with a single '#' character. 7 | # 8 | # 9 | # 10 | #:INTERNAL: Internal services 11 | #echo stream tcp nowait root internal 12 | #echo dgram udp wait root internal 13 | #chargen stream tcp nowait root internal 14 | #chargen dgram udp wait root internal 15 | #discard stream tcp nowait root internal 16 | #discard dgram udp wait root internal 17 | #daytime stream tcp nowait root internal 18 | #daytime dgram udp wait root internal 19 | #time stream tcp nowait root internal 20 | #time dgram udp wait root internal 21 | telnet stream tcp nowait root telnetd telnetd -i 22 | ftp stream tcp nowait root ftpd ftpd -w 23 | -------------------------------------------------------------------------------- /em-os/project-spec/configs/init-ifupdown/interfaces: -------------------------------------------------------------------------------- 1 | # /etc/network/interfaces -- configuration file for ifup(8), ifdown(8) 2 | 3 | # The loopback interface 4 | auto lo 5 | iface lo inet loopback 6 | 7 | # Wireless interfaces 8 | iface wlan0 inet dhcp 9 | wireless_mode managed 10 | wireless_essid any 11 | wpa-driver wext 12 | wpa-conf /etc/wpa_supplicant.conf 13 | 14 | iface atml0 inet dhcp 15 | 16 | # Wired or wireless interfaces 17 | auto eth0 18 | iface eth0 inet dhcp 19 | iface eth1 inet dhcp 20 | 21 | # Ethernet/RNDIS gadget (g_ether) 22 | # ... or on host side, usbnet and random hwaddr 23 | iface usb0 inet static 24 | address 192.168.7.2 25 | netmask 255.255.255.0 26 | network 192.168.7.0 27 | gateway 192.168.7.1 28 | 29 | # Bluetooth networking 30 | iface bnep0 inet dhcp 31 | 32 | -------------------------------------------------------------------------------- /em-os/project-spec/configs/plnx_syshw_data: -------------------------------------------------------------------------------- 1 | device_id: 7z020 2 | hw_design_name: controller_firmware_top 3 | processor: 4 | ps7_cortexa9_0: 5 | arch: arm 6 | ip_name: ps7_cortexa9 7 | slaves_strings: ps7_afi_0 ps7_afi_1 ps7_afi_2 ps7_afi_3 ps7_coresight_comp_0 ps7_ddr_0 ps7_ddrc_0 ps7_dev_cfg_0 ps7_dma_ns ps7_dma_s ps7_ethernet_0 ps7_globaltimer_0 ps7_gpio_0 ps7_gpv_0 ps7_intc_dist_0 ps7_iop_bus_config_0 ps7_l2cachec_0 ps7_ocmc_0 ps7_pl310_0 ps7_pmu_0 ps7_qspi_0 ps7_qspi_linear_0 ps7_ram_0 ps7_ram_1 ps7_scuc_0 ps7_scugic_0 ps7_scutimer_0 ps7_scuwdt_0 ps7_sd_0 ps7_slcr_0 ps7_uart_1 ps7_xadc_0 8 | slaves: 9 | ps7_ddr_0_bankless: 10 | device_type: memory 11 | ip_name: ps7_ddr 12 | baseaddr: 0x0 13 | highaddr: 0x3FFFFFFF 14 | ps7_uart_1: 15 | device_type: serial 16 | ip_name: ps7_uart 17 | ps7_ethernet_0: 18 | device_type: ethernet 19 | ip_name: ps7_ethernet 20 | ps7_qspi_0_bankless: 21 | device_type: flash 22 | ip_name: ps7_qspi 23 | ps7_sd_0: 24 | device_type: sd 25 | ip_name: ps7_sdio 26 | ps7_afi_0: 27 | ip_name: ps7_afi 28 | ps7_afi_1: 29 | ip_name: ps7_afi 30 | ps7_afi_2: 31 | ip_name: ps7_afi 32 | ps7_afi_3: 33 | ip_name: ps7_afi 34 | ps7_coresight_comp_0: 35 | ip_name: ps7_coresight_comp 36 | ps7_ddr_0: 37 | ip_name: ps7_ddr 38 | ps7_ddrc_0: 39 | ip_name: ps7_ddrc 40 | ps7_dev_cfg_0: 41 | ip_name: ps7_dev_cfg 42 | ps7_dma_ns: 43 | ip_name: ps7_dma 44 | ps7_dma_s: 45 | ip_name: ps7_dma 46 | ps7_globaltimer_0: 47 | ip_name: ps7_globaltimer 48 | ps7_gpio_0: 49 | ip_name: ps7_gpio 50 | ps7_gpv_0: 51 | ip_name: ps7_gpv 52 | ps7_intc_dist_0: 53 | ip_name: ps7_intc_dist 54 | ps7_iop_bus_config_0: 55 | ip_name: ps7_iop_bus_config 56 | ps7_l2cachec_0: 57 | ip_name: ps7_l2cachec 58 | ps7_ocmc_0: 59 | ip_name: ps7_ocmc 60 | ps7_pl310_0: 61 | ip_name: ps7_pl310 62 | ps7_pmu_0: 63 | ip_name: ps7_pmu 64 | ps7_qspi_0: 65 | ip_name: ps7_qspi 66 | ps7_qspi_linear_0: 67 | ip_name: ps7_qspi_linear 68 | ps7_ram_0: 69 | ip_name: ps7_ram 70 | ps7_ram_1: 71 | ip_name: ps7_ram 72 | ps7_scuc_0: 73 | ip_name: ps7_scuc 74 | ps7_scugic_0: 75 | ip_name: ps7_scugic 76 | ps7_scutimer_0: 77 | ip_name: ps7_scutimer 78 | ps7_scuwdt_0: 79 | ip_name: ps7_scuwdt 80 | ps7_slcr_0: 81 | ip_name: ps7_slcr 82 | ps7_xadc_0: 83 | ip_name: ps7_xadc 84 | -------------------------------------------------------------------------------- /em-os/project-spec/configs/plnxtool.conf: -------------------------------------------------------------------------------- 1 | # PetaLinux Tool Auto generated file 2 | 3 | # Generic variables 4 | SOURCE_MIRROR_URL = "http://petalinux.xilinx.com/sswreleases/rel-v${PETALINUX_MAJOR_VER}/downloads" 5 | PREMIRRORS = "\ 6 | cvs://.*/.* http://petalinux.xilinx.com/sswreleases/rel-v2023/downloads \ 7 | svn://.*/.* http://petalinux.xilinx.com/sswreleases/rel-v2023/downloads \ 8 | git://.*/.* http://petalinux.xilinx.com/sswreleases/rel-v2023/downloads \ 9 | gitsm://.*/.* http://petalinux.xilinx.com/sswreleases/rel-v2023/downloads \ 10 | hg://.*/.* http://petalinux.xilinx.com/sswreleases/rel-v2023/downloads \ 11 | bzr://.*/.* http://petalinux.xilinx.com/sswreleases/rel-v2023/downloads \ 12 | p4://.*/.* http://petalinux.xilinx.com/sswreleases/rel-v2023/downloads \ 13 | osc://.*/.* http://petalinux.xilinx.com/sswreleases/rel-v2023/downloads \ 14 | https?://.*/.* http://petalinux.xilinx.com/sswreleases/rel-v2023/downloads \ 15 | ftp://.*/.* http://petalinux.xilinx.com/sswreleases/rel-v2023/downloads \ 16 | npm://.*/?.* http://petalinux.xilinx.com/sswreleases/rel-v2023/downloads \ 17 | s3://.*/.* http://petalinux.xilinx.com/sswreleases/rel-v2023/downloads \ 18 | crate://.*/.* http://petalinux.xilinx.com/sswreleases/rel-v2023/downloads \ 19 | " 20 | # Sstate mirror settings 21 | SSTATE_MIRRORS = " \ 22 | file://.* http://petalinux.xilinx.com/sswreleases/rel-v${PETALINUX_MAJOR_VER}/arm/sstate-cache/PATH;downloadfilename=PATH \n \ 23 | " 24 | 25 | MACHINE = "zynq-generic-7z020" 26 | TMPDIR = "${PROOT}/build/tmp" 27 | UNINATIVE_URL = "file:///home/excessive/github/controller-software/controller-software/em-os/components/yocto/downloads/uninative/5fab9a5c97fc73a21134e5a81f74498cbaecda75d56aab971c934e0b803bcc00/" 28 | PACKAGE_CLASSES = "package_rpm" 29 | DL_DIR = "${TOPDIR}/downloads" 30 | SSTATE_DIR = "${TOPDIR}/sstate-cache" 31 | hostname:pn-base-files = "em-os" 32 | PETALINUX_PRODUCT:pn-base-files-plnx = "em-os" 33 | DISTRO_VERSION:pn-base-files-plnx = "1.00" 34 | 35 | # SDK path variables 36 | XILINX_SDK_TOOLCHAIN = "/home/excessive/petalinux/tools/xsct" 37 | USE_XSCT_TARBALL = "0" 38 | 39 | # PetaLinux tool linux-xlnx variables 40 | RRECOMMENDS:${KERNEL_PACKAGE_NAME}-base = "" 41 | 42 | # PetaLinux tool device-tree variables 43 | XSCTH_WS:pn-device-tree = "${PROOT}/components/plnx_workspace/device-tree" 44 | EXTRA_DT_FILES = "" 45 | 46 | # PetaLinux tool U-boot variables 47 | 48 | # PetaLinux tool FSBL variables 49 | YAML_COMPILER_FLAGS:append:pn-fsbl-firmware = " " 50 | KERNEL_IMAGETYPE = "zImage" 51 | KERNEL_ALT_IMAGETYPE = "uImage" 52 | 53 | # PetaLinux tool FIT Variables 54 | KERNEL_CLASSES:append = " kernel-fitimage" 55 | KERNEL_IMAGETYPES:append = " fitImage vmlinux" 56 | 57 | #Add u-boot-xlnx-scr Variables 58 | BOOTMODE = "generic" 59 | BOOTFILE_EXT = "" 60 | RAMDISK_IMAGE:${MACHINE} = "rootfs.cpio.gz.u-boot" 61 | RAMDISK_IMAGE1:${MACHINE} = "ramdisk.cpio.gz.u-boot" 62 | KERNEL_IMAGE:${MACHINE} = "uImage" 63 | DEVICETREE_OFFSET:${MACHINE} = "0x100000" 64 | KERNEL_OFFSET:${MACHINE} = "0x200000" 65 | RAMDISK_OFFSET:${MACHINE} = "0x4000000" 66 | QSPI_KERNEL_OFFSET:${MACHINE} = "0xA00000" 67 | QSPI_KERNEL_SIZE:${MACHINE} = "0x600000" 68 | QSPI_RAMDISK_OFFSET:${MACHINE} = "0x1000000" 69 | QSPI_RAMDISK_SIZE:${MACHINE} = "0xF80000" 70 | QSPI_FIT_IMAGE_OFFSET:${MACHINE} = "0xA80000" 71 | QSPI_FIT_IMAGE_SIZE:${MACHINE} = "0x1500000" 72 | NAND_KERNEL_OFFSET:${MACHINE} = "0x1000000" 73 | NAND_KERNEL_SIZE:${MACHINE} = "0x3200000" 74 | NAND_RAMDISK_OFFSET:${MACHINE} = "0x4600000" 75 | NAND_RAMDISK_SIZE:${MACHINE} = "0x3200000" 76 | NAND_FIT_IMAGE_OFFSET:${MACHINE} = "0x1080000" 77 | NAND_FIT_IMAGE_SIZE:${MACHINE} = "0x6400000" 78 | FIT_IMAGE:${MACHINE} = "image.ub" 79 | FIT_IMAGE_OFFSET:${MACHINE} = "0x10000000" 80 | PRE_BOOTENV:${MACHINE} = "" 81 | IMAGE_FSTYPES:zynq = "cpio cpio.gz cpio.gz.u-boot ext4 tar.gz jffs2" 82 | 83 | #Add EXTRA_IMAGEDEPENDS 84 | EXTRA_IMAGEDEPENDS:append = " virtual/bootloader virtual/fsbl u-boot-xlnx-scr" 85 | EXTRA_IMAGEDEPENDS:remove = "virtual/boot-bin" 86 | SPL_BINARY = "" 87 | 88 | #SDK variables 89 | SDK_EXT_TYPE = "minimal" 90 | SDK_INCLUDE_BUILDTOOLS = "0" 91 | 92 | # deploy class variables 93 | INHERIT += "plnx-deploy" 94 | PLNX_DEPLOY_DIR = "${PROOT}/images/linux" 95 | PACKAGE_DTB_NAME = "" 96 | PACKAGE_FITIMG_NAME = "image.ub" 97 | EXTRA_FILESLIST:append = " /home/excessive/github/controller-software/controller-software/em-os/project-spec/configs/config:config /home/excessive/github/controller-software/controller-software/em-os/project-spec/hw-description/controller_firmware_top.bit:system.bit" 98 | 99 | #Below variables helps to add bbappend changes when this file included 100 | WITHIN_PLNX_FLOW = "1" 101 | SYSCONFIG_DIR = "/home/excessive/github/controller-software/controller-software/em-os/project-spec/configs" 102 | 103 | #Rootfs configs 104 | INHERIT += "plnx-deploy extrausers" 105 | INIT_MANAGER = "sysvinit" 106 | 107 | COMMON_FEATURES:pn-petalinux-image-minimal = "\ 108 | ssh-server-openssh \ 109 | hwcodecs \ 110 | package-management \ 111 | " 112 | IMAGE_LINGUAS:zynq = " " 113 | 114 | IMAGE_INSTALL:pn-petalinux-image-minimal = "\ 115 | kernel-modules \ 116 | e2fsprogs-mke2fs \ 117 | fpga-manager-script \ 118 | mtd-utils \ 119 | can-utils \ 120 | nfs-utils \ 121 | pciutils \ 122 | run-postinsts \ 123 | udev-extraconf \ 124 | linux-xlnx-udev-rules \ 125 | libstdc++ \ 126 | libstdc++-dev \ 127 | gdb \ 128 | gdb-dbg \ 129 | gdb-dev \ 130 | gdbserver \ 131 | packagegroup-core-boot \ 132 | tcf-agent \ 133 | bridge-utils \ 134 | u-boot-tools \ 135 | " 136 | EXTRA_USERS_PARAMS = "groupadd -r aie;useradd -p '\$6\$xx\$7/H0GPMdqRWckn1Ajj09mDM3szu1YxmcKLW7DDXkcDmDbvVa3Oq3j70Jou6HOza3.cL6TW85FE22.tlQw1vrL1' em-os;usermod -a -G audio em-os;usermod -a -G video em-os;usermod -a -G aie em-os;usermod -a -G input em-os; \ 137 | " 138 | USERADDEXTENSION:append = " plnx-useradd-sudoers" 139 | EXTRA_USERS_SUDOERS = "em-os ALL=(ALL) ALL;" 140 | -------------------------------------------------------------------------------- /em-os/project-spec/configs/rootfs.wks: -------------------------------------------------------------------------------- 1 | # Description: Creates a partitioned SD card image. Boot files 2 | # are located in the first vfat partition. Rootfs will be in second ext4 partition. 3 | 4 | part /boot --source bootimg-partition --ondisk mmcblk0 --fstype=vfat --label boot --active --align 4 --fixed-size 256M 5 | part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --label root --align 4 --size 2G 6 | -------------------------------------------------------------------------------- /em-os/project-spec/configs/rootfsconfigs/Kconfig.user: -------------------------------------------------------------------------------- 1 | menu "user packages " 2 | config gpio-demo 3 | bool "gpio-demo" 4 | help 5 | 6 | config peekpoke 7 | bool "peekpoke" 8 | help 9 | 10 | endmenu 11 | -------------------------------------------------------------------------------- /em-os/project-spec/configs/rootfsconfigs/user-rootfsconfig: -------------------------------------------------------------------------------- 1 | #Note: Mention Each package in individual line 2 | #These packages will get added into rootfs menu entry 3 | 4 | CONFIG_gpio-demo 5 | CONFIG_peekpoke 6 | -------------------------------------------------------------------------------- /em-os/project-spec/configs/systemd-conf/wired.network: -------------------------------------------------------------------------------- 1 | [Match] 2 | Type=ether 3 | Name=!veth* 4 | KernelCommandLine=!nfsroot 5 | KernelCommandLine=!ip 6 | 7 | [Network] 8 | DHCP=yes 9 | 10 | [DHCP] 11 | UseMTU=yes 12 | RouteMetric=10 13 | ClientIdentifier=mac 14 | -------------------------------------------------------------------------------- /em-os/project-spec/configs/zynq-generic-7z020.conf: -------------------------------------------------------------------------------- 1 | #@TYPE: Machine 2 | #@NAME: zynq-generic-7z020 3 | #@DESCRIPTION: Machine configuration for the zynq-generic-7z020 boards. 4 | 5 | #### Preamble 6 | MACHINEOVERRIDES =. "${@['', 'zynq-generic-7z020:']['zynq-generic-7z020' !='${MACHINE}']}" 7 | #### Regular settings follow 8 | 9 | # Add system XSA 10 | HDF_EXT = "xsa" 11 | HDF_BASE = "file://" 12 | HDF_PATH = "/home/excessive/github/controller-software/controller-software/em-os/project-spec/hw-description/system.xsa" 13 | 14 | # Yocto device-tree variables 15 | YAML_CONSOLE_DEVICE_CONFIG:pn-device-tree ?= "ps7_uart_1" 16 | YAML_MAIN_MEMORY_CONFIG:pn-device-tree = "PS7_DDR_0" 17 | DT_PADDING_SIZE:pn-device-tree ?= "0x1000" 18 | DTC_FLAGS:pn-device-tree ?= "-@" 19 | YAML_DT_BOARD_FLAGS:zynq-generic-7z020 = "{BOARD template}" 20 | 21 | # Yocto linux-xlnx variables 22 | 23 | # Yocto u-boot-xlnx variables 24 | UBOOT_MACHINE ?= "xilinx_zynq_virt_defconfig" 25 | HAS_PLATFORM_INIT:append = " xilinx_zynq_virt_defconfig" 26 | 27 | # Yocto FSBL variables 28 | YAML_SERIAL_CONSOLE_STDIN:pn-fsbl-firmware ?= "ps7_uart_1" 29 | YAML_SERIAL_CONSOLE_STDOUT:pn-fsbl-firmware ?= "ps7_uart_1" 30 | 31 | # Yocto KERNEL Variables 32 | UBOOT_ENTRYPOINT = "0x200000" 33 | UBOOT_LOADADDRESS = "0x200000" 34 | KERNEL_EXTRA_ARGS:zynq += "UIMAGE_LOADADDR=${UBOOT_ENTRYPOINT}" 35 | 36 | #Set DDR Base address for u-boot-xlnx-scr variables 37 | DDR_BASEADDR = "0x0" 38 | SKIP_APPEND_BASEADDR = "0" 39 | 40 | # zynq-generic-7z020 Serial Console 41 | SERIAL_CONSOLES = "115200;ttyPS0" 42 | SERIAL_CONSOLES_CHECK = "${SERIAL_CONSOLES}" 43 | YAML_SERIAL_CONSOLE_BAUDRATE = "115200" 44 | 45 | # Required generic machine inclusion 46 | require conf/machine/zynq-generic.conf 47 | 48 | # Yocto MACHINE_FEATURES Variable 49 | MACHINE_FEATURES += "fpga-overlay" 50 | 51 | #### No additional settings should be after the Postamble 52 | #### Postamble 53 | PACKAGE_EXTRA_ARCHS:append = "${@['', 'zynq_generic_7z020']['zynq-generic-7z020' != '${MACHINE}']}" 54 | -------------------------------------------------------------------------------- /em-os/project-spec/hw-description/controller_firmware_top.bit: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExcessiveMotion/controller-software/0503bb292ecb54b9c41ecbcdc22a93bbeb4c36b0/em-os/project-spec/hw-description/controller_firmware_top.bit -------------------------------------------------------------------------------- /em-os/project-spec/hw-description/metadata: -------------------------------------------------------------------------------- 1 | HARDWARE_SOURCE= 2 | -------------------------------------------------------------------------------- /em-os/project-spec/hw-description/ps7_init.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * 3 | * Copyright (C) 2010-2020 Xilinx, Inc. All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | ******************************************************************************/ 6 | /****************************************************************************/ 7 | /** 8 | * 9 | * @file ps7_init.h 10 | * 11 | * This file can be included in FSBL code 12 | * to get prototype of ps7_init() function 13 | * and error codes 14 | * 15 | *****************************************************************************/ 16 | 17 | 18 | 19 | 20 | #ifdef __cplusplus 21 | extern "C" { 22 | #endif 23 | 24 | 25 | //typedef unsigned int u32; 26 | 27 | 28 | /** do we need to make this name more unique ? **/ 29 | //extern u32 ps7_init_data[]; 30 | extern unsigned long * ps7_ddr_init_data; 31 | extern unsigned long * ps7_mio_init_data; 32 | extern unsigned long * ps7_pll_init_data; 33 | extern unsigned long * ps7_clock_init_data; 34 | extern unsigned long * ps7_peripherals_init_data; 35 | 36 | 37 | 38 | #define OPCODE_EXIT 0U 39 | #define OPCODE_CLEAR 1U 40 | #define OPCODE_WRITE 2U 41 | #define OPCODE_MASKWRITE 3U 42 | #define OPCODE_MASKPOLL 4U 43 | #define OPCODE_MASKDELAY 5U 44 | #define NEW_PS7_ERR_CODE 1 45 | 46 | /* Encode number of arguments in last nibble */ 47 | #define EMIT_EXIT() ( (OPCODE_EXIT << 4 ) | 0 ) 48 | #define EMIT_CLEAR(addr) ( (OPCODE_CLEAR << 4 ) | 1 ) , addr 49 | #define EMIT_WRITE(addr,val) ( (OPCODE_WRITE << 4 ) | 2 ) , addr, val 50 | #define EMIT_MASKWRITE(addr,mask,val) ( (OPCODE_MASKWRITE << 4 ) | 3 ) , addr, mask, val 51 | #define EMIT_MASKPOLL(addr,mask) ( (OPCODE_MASKPOLL << 4 ) | 2 ) , addr, mask 52 | #define EMIT_MASKDELAY(addr,mask) ( (OPCODE_MASKDELAY << 4 ) | 2 ) , addr, mask 53 | 54 | /* Returns codes of PS7_Init */ 55 | #define PS7_INIT_SUCCESS (0) // 0 is success in good old C 56 | #define PS7_INIT_CORRUPT (1) // 1 the data is corrupted, and slcr reg are in corrupted state now 57 | #define PS7_INIT_TIMEOUT (2) // 2 when a poll operation timed out 58 | #define PS7_POLL_FAILED_DDR_INIT (3) // 3 when a poll operation timed out for ddr init 59 | #define PS7_POLL_FAILED_DMA (4) // 4 when a poll operation timed out for dma done bit 60 | #define PS7_POLL_FAILED_PLL (5) // 5 when a poll operation timed out for pll sequence init 61 | 62 | 63 | /* Silicon Versions */ 64 | #define PCW_SILICON_VERSION_1 0 65 | #define PCW_SILICON_VERSION_2 1 66 | #define PCW_SILICON_VERSION_3 2 67 | 68 | /* This flag to be used by FSBL to check whether ps7_post_config() proc exixts */ 69 | #define PS7_POST_CONFIG 70 | 71 | /* Freq of all peripherals */ 72 | 73 | #define APU_FREQ 666666687 74 | #define DDR_FREQ 533333374 75 | #define DCI_FREQ 10158730 76 | #define QSPI_FREQ 200000000 77 | #define SMC_FREQ 10000000 78 | #define ENET0_FREQ 125000000 79 | #define ENET1_FREQ 10000000 80 | #define USB0_FREQ 60000000 81 | #define USB1_FREQ 60000000 82 | #define SDIO_FREQ 100000000 83 | #define UART_FREQ 100000000 84 | #define SPI_FREQ 10000000 85 | #define I2C_FREQ 111111115 86 | #define WDT_FREQ 111111115 87 | #define TTC_FREQ 50000000 88 | #define CAN_FREQ 10000000 89 | #define PCAP_FREQ 200000000 90 | #define TPIU_FREQ 200000000 91 | #define FPGA0_FREQ 25000000 92 | #define FPGA1_FREQ 50000000 93 | #define FPGA2_FREQ 100000000 94 | #define FPGA3_FREQ 200000000 95 | 96 | 97 | /* For delay calculation using global registers*/ 98 | #define SCU_GLOBAL_TIMER_COUNT_L32 0xF8F00200 99 | #define SCU_GLOBAL_TIMER_COUNT_U32 0xF8F00204 100 | #define SCU_GLOBAL_TIMER_CONTROL 0xF8F00208 101 | #define SCU_GLOBAL_TIMER_AUTO_INC 0xF8F00218 102 | 103 | int ps7_config( unsigned long*); 104 | int ps7_init(); 105 | int ps7_post_config(); 106 | int ps7_debug(); 107 | char* getPS7MessageInfo(unsigned key); 108 | 109 | void perf_start_clock(void); 110 | void perf_disable_clock(void); 111 | void perf_reset_clock(void); 112 | void perf_reset_and_start_timer(); 113 | int get_number_of_cycles_for_delay(unsigned int delay); 114 | #ifdef __cplusplus 115 | } 116 | #endif 117 | 118 | -------------------------------------------------------------------------------- /em-os/project-spec/hw-description/ps7_init_gpl.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * 3 | * Copyright (C) 2010-2020 4 | * 5 | * This program is free software; you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 2 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License along 16 | * with this program; if not, see 17 | * 18 | * 19 | ******************************************************************************/ 20 | /****************************************************************************/ 21 | /** 22 | * 23 | * @file ps7_init_gpl.h 24 | * 25 | * This file can be included in FSBL code 26 | * to get prototype of ps7_init() function 27 | * and error codes 28 | * 29 | *****************************************************************************/ 30 | 31 | 32 | 33 | 34 | #ifdef __cplusplus 35 | extern "C" { 36 | #endif 37 | 38 | 39 | //typedef unsigned int u32; 40 | 41 | 42 | /** do we need to make this name more unique ? **/ 43 | //extern u32 ps7_init_data[]; 44 | extern unsigned long * ps7_ddr_init_data; 45 | extern unsigned long * ps7_mio_init_data; 46 | extern unsigned long * ps7_pll_init_data; 47 | extern unsigned long * ps7_clock_init_data; 48 | extern unsigned long * ps7_peripherals_init_data; 49 | 50 | 51 | 52 | #define OPCODE_EXIT 0U 53 | #define OPCODE_CLEAR 1U 54 | #define OPCODE_WRITE 2U 55 | #define OPCODE_MASKWRITE 3U 56 | #define OPCODE_MASKPOLL 4U 57 | #define OPCODE_MASKDELAY 5U 58 | #define NEW_PS7_ERR_CODE 1 59 | 60 | /* Encode number of arguments in last nibble */ 61 | #define EMIT_EXIT() ( (OPCODE_EXIT << 4 ) | 0 ) 62 | #define EMIT_CLEAR(addr) ( (OPCODE_CLEAR << 4 ) | 1 ) , addr 63 | #define EMIT_WRITE(addr,val) ( (OPCODE_WRITE << 4 ) | 2 ) , addr, val 64 | #define EMIT_MASKWRITE(addr,mask,val) ( (OPCODE_MASKWRITE << 4 ) | 3 ) , addr, mask, val 65 | #define EMIT_MASKPOLL(addr,mask) ( (OPCODE_MASKPOLL << 4 ) | 2 ) , addr, mask 66 | #define EMIT_MASKDELAY(addr,mask) ( (OPCODE_MASKDELAY << 4 ) | 2 ) , addr, mask 67 | 68 | /* Returns codes of PS7_Init */ 69 | #define PS7_INIT_SUCCESS (0) // 0 is success in good old C 70 | #define PS7_INIT_CORRUPT (1) // 1 the data is corrupted, and slcr reg are in corrupted state now 71 | #define PS7_INIT_TIMEOUT (2) // 2 when a poll operation timed out 72 | #define PS7_POLL_FAILED_DDR_INIT (3) // 3 when a poll operation timed out for ddr init 73 | #define PS7_POLL_FAILED_DMA (4) // 4 when a poll operation timed out for dma done bit 74 | #define PS7_POLL_FAILED_PLL (5) // 5 when a poll operation timed out for pll sequence init 75 | 76 | 77 | /* Silicon Versions */ 78 | #define PCW_SILICON_VERSION_1 0 79 | #define PCW_SILICON_VERSION_2 1 80 | #define PCW_SILICON_VERSION_3 2 81 | 82 | /* This flag to be used by FSBL to check whether ps7_post_config() proc exixts */ 83 | #define PS7_POST_CONFIG 84 | 85 | /* Freq of all peripherals */ 86 | 87 | #define APU_FREQ 666666687 88 | #define DDR_FREQ 533333374 89 | #define DCI_FREQ 10158730 90 | #define QSPI_FREQ 200000000 91 | #define SMC_FREQ 10000000 92 | #define ENET0_FREQ 125000000 93 | #define ENET1_FREQ 10000000 94 | #define USB0_FREQ 60000000 95 | #define USB1_FREQ 60000000 96 | #define SDIO_FREQ 100000000 97 | #define UART_FREQ 100000000 98 | #define SPI_FREQ 10000000 99 | #define I2C_FREQ 111111115 100 | #define WDT_FREQ 111111115 101 | #define TTC_FREQ 50000000 102 | #define CAN_FREQ 10000000 103 | #define PCAP_FREQ 200000000 104 | #define TPIU_FREQ 200000000 105 | #define FPGA0_FREQ 25000000 106 | #define FPGA1_FREQ 50000000 107 | #define FPGA2_FREQ 100000000 108 | #define FPGA3_FREQ 200000000 109 | 110 | 111 | /* For delay calculation using global registers*/ 112 | #define SCU_GLOBAL_TIMER_COUNT_L32 0xF8F00200 113 | #define SCU_GLOBAL_TIMER_COUNT_U32 0xF8F00204 114 | #define SCU_GLOBAL_TIMER_CONTROL 0xF8F00208 115 | #define SCU_GLOBAL_TIMER_AUTO_INC 0xF8F00218 116 | 117 | int ps7_config( unsigned long*); 118 | int ps7_init(); 119 | int ps7_post_config(); 120 | int ps7_debug(); 121 | char* getPS7MessageInfo(unsigned key); 122 | 123 | void perf_start_clock(void); 124 | void perf_disable_clock(void); 125 | void perf_reset_clock(void); 126 | void perf_reset_and_start_timer(); 127 | int get_number_of_cycles_for_delay(unsigned int delay); 128 | #ifdef __cplusplus 129 | } 130 | #endif 131 | 132 | -------------------------------------------------------------------------------- /em-os/project-spec/hw-description/system.xsa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExcessiveMotion/controller-software/0503bb292ecb54b9c41ecbcdc22a93bbeb4c36b0/em-os/project-spec/hw-description/system.xsa -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/COPYING.MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in 9 | all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 17 | THE SOFTWARE. 18 | -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/README: -------------------------------------------------------------------------------- 1 | This README file contains information on the contents of the 2 | meta-user layer. 3 | 4 | Please see the corresponding sections below for details. 5 | 6 | 7 | Dependencies 8 | ============ 9 | 10 | This layer depends on: 11 | 12 | URI: git://git.openembedded.org/bitbake 13 | branch: master 14 | 15 | URI: git://git.openembedded.org/openembedded-core 16 | layers: meta 17 | branch: master 18 | 19 | URI: git://git.yoctoproject.org/xxxx 20 | layers: xxxx 21 | branch: master 22 | 23 | 24 | Patches 25 | ======= 26 | 27 | Please submit any patches against the meta-user layer to the 28 | xxxx mailing list (xxxx@zzzz.org) and cc: the maintainer: 29 | 30 | Maintainer: XXX YYYYYY 31 | 32 | 33 | Table of Contents 34 | ================= 35 | 36 | I. Adding the meta-user layer to your build 37 | II. Misc 38 | 39 | 40 | I. Adding the meta-user layer to your build 41 | ================================================= 42 | 43 | --- replace with specific instructions for the meta-user layer --- 44 | 45 | In order to use this layer, you need to make the build system aware of 46 | it. 47 | 48 | Assuming the meta-user layer exists at the top-level of your 49 | yocto build tree, you can add it to the build system by adding the 50 | location of the meta-user layer to bblayers.conf, along with any 51 | other layers needed. e.g.: 52 | 53 | BBLAYERS ?= " \ 54 | /path/to/yocto/meta \ 55 | /path/to/yocto/meta-poky \ 56 | /path/to/yocto/meta-yocto-bsp \ 57 | /path/to/yocto/meta-meta-user \ 58 | " 59 | 60 | 61 | II. Misc 62 | ======== 63 | 64 | --- replace with specific information about the meta-user layer --- 65 | -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/conf/layer.conf: -------------------------------------------------------------------------------- 1 | # We have a conf and classes directory, add to BBPATH 2 | BBPATH .= ":${LAYERDIR}" 3 | 4 | # We have recipes-* directories, add to BBFILES 5 | BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \ 6 | ${LAYERDIR}/recipes-*/*/*.bbappend" 7 | 8 | # Define dynamic layers 9 | BBFILES_DYNAMIC += " \ 10 | xilinx-tools:${LAYERDIR}/meta-xilinx-tools/recipes-*/*/*.bbappend \ 11 | " 12 | 13 | BBFILE_COLLECTIONS += "meta-user" 14 | BBFILE_PATTERN_meta-user = "^${LAYERDIR}/" 15 | BBFILE_PRIORITY_meta-user = "7" 16 | LAYERSERIES_COMPAT_meta-user = "langdale" 17 | -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/conf/petalinuxbsp.conf: -------------------------------------------------------------------------------- 1 | #User Configuration 2 | 3 | #OE_TERMINAL = "tmux" 4 | IMAGE_BOOT_FILES:zynq = "BOOT.BIN,image.ub,boot.scr" 5 | -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/conf/user-rootfsconfig: -------------------------------------------------------------------------------- 1 | #Note: Mention Each package in individual line 2 | #These packages will get added into rootfs menu entry 3 | 4 | CONFIG_gpio-demo 5 | CONFIG_peekpoke 6 | -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/meta-xilinx-tools/recipes-bsp/uboot-device-tree/files/system-user.dtsi: -------------------------------------------------------------------------------- 1 | /include/ "system-conf.dtsi" 2 | / { 3 | }; 4 | -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/meta-xilinx-tools/recipes-bsp/uboot-device-tree/uboot-device-tree.bbappend: -------------------------------------------------------------------------------- 1 | FILESEXTRAPATHS:prepend := "${THISDIR}/files:" 2 | 3 | SRC_URI:append = " file://system-user.dtsi" 4 | 5 | -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/recipes-bsp/device-tree/device-tree-sdt.inc: -------------------------------------------------------------------------------- 1 | SRC_URI:append = " ${@" ".join(["file://%s" % f for f in (d.getVar('EXTRA_DT_FILES') or "").split()])}" 2 | 3 | # We need the deployed output 4 | PROC_TUNE:versal = "cortexa72" 5 | PROC_TUNE:zynqmp = "cortexa53" 6 | APU_DT_FILE_NAME ?= "${PROC_TUNE}-${SOC_FAMILY}-linux.dts" 7 | 8 | EXTRA_DT_FILES ??= "" 9 | EXTRA_DTFILE_PREFIX ??= "system" 10 | EXTRA_DTFILES_BUNDLE ??= "" 11 | 12 | do_configure:append () { 13 | if ! grep -e "^/include/ \"system-user.dtsi\"$" "${DT_FILES_PATH}/${APU_DT_FILE_NAME}"; then 14 | echo "/include/ \"system-user.dtsi\"" >> ${DT_FILES_PATH}/${APU_DT_FILE_NAME} 15 | fi 16 | for f in ${EXTRA_DT_FILES}; do 17 | cp ${WORKDIR}/${f} ${DT_FILES_PATH}/ 18 | done 19 | } 20 | 21 | devicetree_do_compile:append() { 22 | import subprocess 23 | 24 | apu_dt_file = d.getVar('APU_DT_FILE_NAME').replace('.dts', '.dtb') or '' 25 | if not apu_dt_file or not os.path.isfile(apu_dt_file): 26 | return 27 | 28 | if d.getVar('EXTRA_DTFILES_BUNDLE'): 29 | ccdtb_prefix = d.getVar('EXTRA_DTFILE_PREFIX') 30 | extra_dt_files = d.getVar('EXTRA_DT_FILES').split() or [] 31 | for dtsfile in extra_dt_files: 32 | dtname = os.path.splitext(os.path.basename(dtsfile))[0] 33 | if os.path.isfile(f"{dtname}.dtbo"): 34 | fdtargs = ["fdtoverlay", "-o", f"{ccdtb_prefix}-{dtname}.dtb", "-i", apu_dt_file, f"{dtname}.dtbo"] 35 | bb.note("Running {0}".format(" ".join(fdtargs))) 36 | subprocess.run(fdtargs, check = True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 37 | } 38 | -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/recipes-bsp/device-tree/device-tree.bbappend: -------------------------------------------------------------------------------- 1 | FILESEXTRAPATHS:prepend := "${THISDIR}/files:" 2 | 3 | SRC_URI:append = " file://system-user.dtsi" 4 | 5 | require ${@'device-tree-sdt.inc' if d.getVar('SYSTEM_DTFILE') != '' else ''} 6 | -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi: -------------------------------------------------------------------------------- 1 | /include/ "system-conf.dtsi" 2 | / { 3 | 4 | 5 | 6 | 7 | fpga_shared_mem@000F0000 { 8 | #address-cells = <1>; 9 | #size-cells = <1>; 10 | ranges; 11 | 12 | compatible = "generic-uio"; 13 | reg = <0x000F0000 0x00008000>; // 32KB 14 | 15 | // IRQ_F2P[0] high level (memory update start) 16 | // IRQ_F2P[1] high level (memory update done) 17 | // IRQ_F2P[2] high level (DMA cycle start) 18 | // IRQ_F2P[3] high level (DMA cycle done) 19 | }; 20 | 21 | fpga_mem_update_running_irq{ 22 | compatible = "generic-uio"; 23 | interrupts = <0 29 4>; 24 | interrupt-names = "mem_update_run"; 25 | interrupt-parent = <&intc>; 26 | }; 27 | 28 | fpga_mem_update_done_irq{ 29 | compatible = "generic-uio"; 30 | interrupts = <0 30 4>; 31 | interrupt-names = "mem_update_done"; 32 | interrupt-parent = <&intc>; 33 | }; 34 | 35 | fpga_cycle_running_irq{ 36 | compatible = "generic-uio"; 37 | interrupts = <0 31 4>; 38 | interrupt-names = "cycle_run"; 39 | interrupt-parent = <&intc>; 40 | }; 41 | 42 | fpga_cycle_done_irq{ 43 | compatible = "generic-uio"; 44 | interrupts = <0 32 4>; 45 | interrupt-names = "cycle_done"; 46 | interrupt-parent = <&intc>; 47 | }; 48 | 49 | 50 | chosen { 51 | bootargs = "console=ttyPS0,115200 earlycon root=/dev/mmcblk0p2 rw rootwait clk_ignore_unused uio_pdrv_genirq.of_id=generic-uio"; 52 | stdout-path = "serial0:115200n8"; 53 | }; 54 | 55 | 56 | }; 57 | 58 | 59 | &sdhci0 { 60 | disable-wp; 61 | }; 62 | -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/recipes-bsp/u-boot/files/bsp.cfg: -------------------------------------------------------------------------------- 1 | CONFIG_SYS_CONFIG_NAME="platform-top" 2 | CONFIG_BOOT_SCRIPT_OFFSET=0x9C0000 3 | -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/recipes-bsp/u-boot/files/platform-top.h: -------------------------------------------------------------------------------- 1 | #if defined(CONFIG_MICROBLAZE) 2 | #include 3 | #define CONFIG_SYS_BOOTM_LEN 0xF000000 4 | #endif 5 | #if defined(CONFIG_ARCH_ZYNQ) 6 | #include 7 | #endif 8 | #if defined(CONFIG_ARCH_ZYNQMP) 9 | #include 10 | #endif 11 | #if defined(CONFIG_ARCH_VERSAL) 12 | #include 13 | #endif 14 | #if defined(CONFIG_ARCH_VERSAL_NET) 15 | #include 16 | #endif 17 | -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/recipes-bsp/u-boot/files/user_2024-10-27-00-48-00.cfg: -------------------------------------------------------------------------------- 1 | CONFIG_SD_BOOT=y 2 | -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/recipes-bsp/u-boot/files/user_2024-11-11-00-54-00.cfg: -------------------------------------------------------------------------------- 1 | CONFIG_SD_BOOT_QSPI=y 2 | -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/recipes-bsp/u-boot/u-boot-xlnx_%.bbappend: -------------------------------------------------------------------------------- 1 | FILESEXTRAPATHS:prepend := "${THISDIR}/files:" 2 | 3 | SRC_URI:append = " file://platform-top.h file://bsp.cfg" 4 | SRC_URI += "file://user_2024-10-27-00-48-00.cfg \ 5 | file://user_2024-11-11-00-54-00.cfg \ 6 | " 7 | 8 | -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/recipes-kernel/linux/linux-xlnx/bsp.cfg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExcessiveMotion/controller-software/0503bb292ecb54b9c41ecbcdc22a93bbeb4c36b0/em-os/project-spec/meta-user/recipes-kernel/linux/linux-xlnx/bsp.cfg -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/recipes-kernel/linux/linux-xlnx/user_2024-11-05-04-03-00.cfg: -------------------------------------------------------------------------------- 1 | CONFIG_STAGING=y 2 | # CONFIG_RTL8192U is not set 3 | # CONFIG_RTLLIB is not set 4 | # CONFIG_RTS5208 is not set 5 | 6 | # 7 | # IIO staging drivers 8 | # 9 | 10 | # 11 | # Accelerometers 12 | # 13 | # CONFIG_ADIS16203 is not set 14 | # CONFIG_ADIS16240 is not set 15 | # end of Accelerometers 16 | 17 | # 18 | # Analog to digital converters 19 | # 20 | # CONFIG_AD7816 is not set 21 | # end of Analog to digital converters 22 | 23 | # 24 | # Analog digital bi-direction converters 25 | # 26 | # CONFIG_ADT7316 is not set 27 | # end of Analog digital bi-direction converters 28 | 29 | # 30 | # Direct Digital Synthesis 31 | # 32 | # CONFIG_AD9832 is not set 33 | # CONFIG_AD9834 is not set 34 | # end of Direct Digital Synthesis 35 | 36 | # 37 | # Network Analyzer, Impedance Converters 38 | # 39 | # CONFIG_AD5933 is not set 40 | # end of Network Analyzer, Impedance Converters 41 | 42 | # 43 | # Active energy metering IC 44 | # 45 | # CONFIG_ADE7854 is not set 46 | # end of Active energy metering IC 47 | 48 | # 49 | # Resolver to digital converters 50 | # 51 | # CONFIG_AD2S1210 is not set 52 | # end of Resolver to digital converters 53 | # end of IIO staging drivers 54 | 55 | # CONFIG_STAGING_MEDIA is not set 56 | # CONFIG_STAGING_BOARD is not set 57 | # CONFIG_LTE_GDM724X is not set 58 | # CONFIG_KS7010 is not set 59 | # CONFIG_PI433 is not set 60 | # CONFIG_XIL_AXIS_FIFO is not set 61 | # CONFIG_FIELDBUS_DEV is not set 62 | # CONFIG_QLGE is not set 63 | # CONFIG_VME_BUS is not set 64 | CONFIG_XILINX_FCLK=y 65 | # CONFIG_XLNX_TSMUX is not set 66 | # CONFIG_XROE_FRAMER is not set 67 | # CONFIG_XROE_TRAFFIC_GEN is not set 68 | # CONFIG_SERIAL_UARTLITE_RS485 is not set 69 | # CONFIG_XILINX_TSN is not set 70 | -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/recipes-kernel/linux/linux-xlnx/user_2024-11-12-22-53-00.cfg: -------------------------------------------------------------------------------- 1 | CONFIG_UIO_PDRV_GENIRQ=y 2 | CONFIG_UIO_DMEM_GENIRQ=y 3 | -------------------------------------------------------------------------------- /em-os/project-spec/meta-user/recipes-kernel/linux/linux-xlnx_%.bbappend: -------------------------------------------------------------------------------- 1 | FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:" 2 | 3 | SRC_URI:append = " file://bsp.cfg" 4 | KERNEL_FEATURES:append = " bsp.cfg" 5 | SRC_URI += "file://user_2024-11-05-04-03-00.cfg \ 6 | file://user_2024-11-12-22-53-00.cfg \ 7 | " 8 | 9 | -------------------------------------------------------------------------------- /petalinux/controller_firmware_top.xsa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ExcessiveMotion/controller-software/0503bb292ecb54b9c41ecbcdc22a93bbeb4c36b0/petalinux/controller_firmware_top.xsa -------------------------------------------------------------------------------- /setup_subst.bat: -------------------------------------------------------------------------------- 1 | subst S: C:\Users\voids\Documents\GitHub\controller-software\controller-firmware --------------------------------------------------------------------------------