diff --git a/CHANGELOG.md b/CHANGELOG.md index e3733cd8..be039e97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ This document summarizes the changes introduced to the code base for each release. +## v1.0.2 + +- Fix task stats printing +- Fix PWM enable hardware driver function for REV E +- Fix FPGA timer address define for REV D +- Fix C drivers of GPIO expansion port IP cores +- Force SDK build error for hardware target mismatch +- Remove local docs folder in favor of docs website +- Rename `AMDC_Logger` method from `clear()` to `empty()` +- Extend `AMDC_Logger` method `auto_find_vars()` to accept regex input + ## v1.0.1 - Fix GPIO subsystem firmware drivers mainly for REV E hardware diff --git a/README.md b/README.md index 4868b319..06da7834 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ *The Advanced Motor Drive Controller (AMDC) is an open-source project from the [Severson Research Group](https://severson.wempec.wisc.edu/) at [UW-Madison](http://www.engr.wisc.edu/department/electrical-computer-engineering/), affiliated with [Wisconsin Electric Machines and Power Electronics Consortium (WEMPEC)](https://wempec.wisc.edu/).* +Learn more at [docs.amdc.dev](https://docs.amdc.dev/). + --- **AMDC-Firmware** is a collection of embedded system code (written in C and Verilog) which runs on the [AMDC Hardware](https://github.com/Severson-Group/AMDC-Hardware) and controls advanced motor systems. It is open-source, high-performance, flexible, and research-oriented. @@ -12,13 +14,13 @@ The target processor is the [Xilinx Zynq-7000 SoC](https://www.xilinx.com/produc ## Getting Started -1. Obtain a working hardware platform -- see the [AMDC Hardware](https://github.com/Severson-Group/AMDC-Hardware) repo for more information. -2. Follow the steps outlined [here](https://github.com/Severson-Group/AMDC-Firmware/blob/develop/docs/Building-and-Running-Firmware.md) to download, build, and run the firmware. -3. Read the [documentation](https://github.com/Severson-Group/AMDC-Firmware/tree/develop/docs) for more insight into the platform. +1. Obtain a working hardware platform -- see the [obtaining hardware](https://docs.amdc.dev/hardware/obtaining-hardware.html) page for more. +2. Follow the steps outlined [here](https://docs.amdc.dev/firmware/xilinx-tools/building-and-running-firmware.html) to download, build, and run the firmware. +3. Read the [documentation](https://docs.amdc.dev/) for more insight into the platform. ## Documentation -Detailed documentation about the AMDC firmware is available in the [`docs/` subfolder](docs/) of this repo. +Detailed documentation about the firmware is available online at: [docs.amdc.dev/firmware](https://docs.amdc.dev/firmware/). ## License @@ -28,6 +30,6 @@ This project is licensed under the BSD-3-Clause License - see the [LICENSE.md](L Want to help build the open-source motor drive platform of choice? Join the people below -- **[Contribute today!](./CONTRIBUTING.md)** - - + + diff --git a/docs/Building-and-Running-Firmware.md b/docs/Building-and-Running-Firmware.md deleted file mode 100644 index a0026424..00000000 --- a/docs/Building-and-Running-Firmware.md +++ /dev/null @@ -1,299 +0,0 @@ - -# Building and Running Firmware - -Following these instructions will get the AMDC firmware environment up and running on your local machine for development and testing purposes. - - -## Required Software - -Firmware development environment needs a few things: - -- Xilinx Vivado 2019.1 and SDK (if you don't have these, [follow these steps to install them](Installing-Xilinx-Tools.md)) -- `em.avnet.com:picozed_7030_fmc2:part0:1.1` board definition - 1. Go here: https://github.com/Severson-Group/AMDC-Firmware/issues/10#issuecomment-847993684 - 2. Download the zip file and unzip it - 3. Move the resulting folder (`picozed_*`) to `C:\Xilinx\Vivado\2019.1\data\boards\board_files\...` - -## Cloning from GitHub - -There are two recomended options for cloning the `AMDC-Firmware` repo from GitHub and creating a local working space on your computer. To choose between them, you must first decide if your user application(s) will be private or open-source. Most likely, your code will be private. This means that you will not contribute it back to the `AMDC-Firmware` repo as an example application. - -### Open-Source Example Applications - -If you are _not_ creating private user applications, i.e. your code will be contributed back to the `AMDC-Firmware` repo as an example application: - -1. Download the `AMDC-Firmware` git repo to your local machine like normal: - 1. `git clone https://github.com/Severson-Group/AMDC-Firmware` -2. Ensure it is in a permanent location (i.e., not `Downloads`) -3. Ensure the path doesn't contain any spaces. - -NOTE: `$REPO_DIR` represents the file system path of the `AMDC-Firmware` repository. - -### Private User Applications - -For the majority of use cases, your user application(s) will be private and reside in a _different_ repo than the `AMDC-Firmware` repo (i.e. your own personal repo): - -1. Create your master repo (which will eventually contain your private code as well as a copy of `AMDC-Firmware`) - 1. Ensure it is in a permanent location (i.e., not `Downloads`) - 2. Ensure the path doesn't contain any spaces. -2. In this repo: - 1. Add a git submodule for the `AMDC-Firmware` repo: `git submodule add https://github.com/Severson-Group/AMDC-Firmware` - 2. Optional (and suggested): add `branch = develop` to `.gitmodules` so your submodule will track the develop branch by default - 3. **Copy** `AMDC-Firmware/sdk/bare/user` to your repo's root directory, and rename (perhaps as "my-AMDC-private-C-code") - -You should now have a master repo with two subfolders: - -``` -my-AMDC-workspace/ <= master repo - AMDC-Firmware/ <= AMDC-Firmware as library - ... - my-AMDC-private-C-code/ <= Your private user C code - ... -``` - -NOTE: In the rest of this document, `$REPO_DIR` represents the file system path of the `AMDC-Firmware` repository, _not your master repo_. - -#### Common `git submodule` commands - -Your repo now contains `AMDC-Firmware` as a _git submodule_. Read about submodules [here](https://git-scm.com/book/en/v2/Git-Tools-Submodules) or [here](https://www.vogella.com/tutorials/GitSubmodules/article.html). The most common command you will use is the **update** command, which updates your submodule from the remote source. Execute this command from your master repo: `git submodule update`. If you have not initialized your submodules, append `--init` to the previous command. - - -## Vivado - -Vivado is used to configure the Zynq-7000 SoC (clocks, pins, etc). All FPGA development happens within Vivado. All users must set up a Vivado project and build a FPGA bitstream. - -If you are not doing anything specialized that would require changes to the FPGA, after following these steps, you do not need to use Vivado again. All your future development will happen within the SDK. - -### Creating Vivado Project - -Only source files and IP is version controlled which mean you must generate a local Vivado project. To do this: - -1. Open Vivado Application -2. `Tools` > `Run Tcl Script...` -3. Select `$REPO_DIR\import_rev*.tcl` script (use appropriate script for the target AMDC hardware) -4. `OK` - -Upon successful project generation, the block diagram will open. If the block diagram does not open, fix the errors and try reimporting project. See the errors by opening the `Tcl Console` pane in Vivado. - -#### Common Errors - -Having spaces in the file system path for the Vivado project is not supported. For example, if your repo is located in your user directory and your username has a space in it: `C:\Users\**John Doe**\Documents\GitHub\AMDC-Firmare`. If this is the case, the project import will fail. Move the repo elsewhere and try again. - -The import script **will not** overwrite the `amdc/` Vivado project folder on disk. If you are trying to regenerate the Vivado project, you must delete the old `amdc/` folder before running the script. - -### Generating Bitstream - -After generating the project itself, you need to generate a bitstream to load into the FPGA. - -1. In Vivado... -2. `PROGRAM AND DEBUG` > `Generate Bitstream` -3. Click through the pop-ups until it starts actually working -4. If there are `Launch Run Critical Messages` about `PCW...`, ignore them and click OK - -This step will take a while (~10 minutes). Upon successful generation, the bitstream is ready to load onto AMDC. This happens in the SDK section of this document. - -### Export Hardware - -You now need to export the hardware from Vivado to the SDK environment. - -1. `File` > `Export` > `Export Hardware...` -2. Make sure to uncheck `Include bitstream` -3. Set location to export to: `$REPO_DIR\sdk` -4. `OK` - -### Open SDK from Vivado - -This is an important step. The first time you generate the FPGA hardware configuration files, etc, you must launch the Xilinx SDK *directly from Vivado*. This sets up a hardware wrapper project which is needed for the firmware, and some environment variables. - -1. `File` > `Launch SDK` -2. Select `$REPO_DIR\sdk` for both "Exported location" and "Workspace" -3. `OK` -4. SDK will open -5. Ensure the project `amdc_rev*_wrapper_hw_platform_0` is in `Project Explorer` - -You may now close Vivado if you do not plan on changing the FPGA HDL. Also, you may now close the SDK. You will need to open it in the next section, but practice opening it directly -- not from Vivado. - - -## Xilinx SDK - -Xilinx SDK (referred to as just SDK) is used to program the DSPs on the Zynq-7000 (i.e., C firmware). You will use the SDK to write your code and compile it. Then, you will use it to program the AMDC with your new code and debug issues. Finally, you can use the SDK to flash the AMDC after code development is complete with a permanent image (i.e., will automatically boot when powered on). - -### Open SDK - -1. Open Xilinx SDK 2019.1 -2. Set workspace to: `$REPO_DIR\sdk` -3. Once open, close the Welcome tab - -### Create BSP Project - -1. `File` > `New` > `Board Support Package` -2. Set `Project name` to "amdc_bsp" -3. `Finish` -4. Pop-up will appear -5. Select `lwip***` -6. Select `xilffs` -7. `OK` -8. The BSP will build - -### Import Projects into SDK - -The SDK workspace will initially be empty (except for `amdc_rev*_wrapper...` from above and new `amdc_bsp`). You need to import the projects you want to use. - -#### Open-source example applications: - -Follow these steps to import projects directly from the core `AMDC-Firmware` repo (i.e. open-source example applications): - -1. `File` > `Open Projects from File System...` -2. `Directory...` -3. Select: `$REPO_DIR\sdk` -4. Ensure all projects are selected -5. `Finish` - -#### Private user applications: - -Follow these steps to import projects from your private user repo: - -1. `File` > `Open Projects from File System...` -2. `Directory...` -3. Select: `your master user repo` / `my-AMDC-private-C-code` -4. `Finish` - -After clicking `Finish`, the SDK will attempt to build the new private user applications. The compilation will fail. If it doesn't, you did not import your private user application project correctly -- delete the project from the SDK and try again until it fails to build. - -Once it fails to build your new imported project, follow the steps below to fix the compilation. This will restructure the compiler / linker so they know where to find the appropriate files. - -### Fix `common` code compilation - -This section explains how to configure the SDK build system to correctly use the AMDC `common` code from the submodule. - -**Only complete these steps if the build failed after you imported the user project!!!** If there were no errors, skip this section. There should be no errors if you have imported the `bare` project as an open-source project (i.e. not a private user application). - -Link `common` folder to project: -1. In the `Project Explorer`, delete `common` folder from `bare` project (if present) -2. Open `bare` project properties -3. `C/C++ General` > `Paths and Symbols` > `Source Location` > `Link Folder...` -4. Check the `Link to folder in the file system` box -5. Browse to `$REPO_DIR\sdk\bare\common` -6. `OK` - -Fix compiler includes to reference `common`: - -7. Change to `Includes` tab -8. `Edit...` on `/bare/common` -9. Click `Workspace...` and select `bare` / `common` -10. `OK` -11. `OK` - -Fix strange SDK issue: - -12. `Edit...` on `/bare/bare` -13. Change directory to `/bare` -14. `OK` - -Fix another strange SDK issue: - -15. `Edit...` on `/bare/amdc_bsp/ps7_cortexa9_0/include` -16. Change directory to `/amdc_bsp/ps7_cortexa9_0/include` -17. `OK` - -Add library path for BSP: - -18. Change to `Library Paths` tab -19. `Add...` > `Workspace...` > `amdc_bsp` / `ps7_cortex9_0` / `lib` -20. `OK` - -Update linker library options: - -21. Change to `C/C++ Build` > `Settings` -22. `Tool Settings` tab -23. `ARM v7 gcc linker` > `Inferred Options` > `Software Platform` -24. Add the following for `Inferred Flags`: `-Wl,--start-group,-lxil,-lgcc,-lc,--end-group` -25. `ARM v7 gcc linker` > `Libraries` -26. Add `m` under `Libraries` -27. Click `OK` to exit properties dialog - -### Build SDK Projects - -SDK will attempt to build the projects you just imported. Wait until all projects are done compiling... Could take a few minutes... - -There shouldn't be any errors. Ensure there are no errors for `amdc_bsp` and your desired application project (i.e. `bare`) - -All done! Ready to program AMDC! - - -## Ensure `git` Synchronized - -At this point, you are done generating code / importing / exporting / etc. Now we will ensure git sees the correct changes. - -### Discard changes to AMDC-Firmware - -Your submodule `AMDC-Firmware` should be clean, i.e. no changes. Chances are, this is not true. Please revert your local changes to `AMDC-Firmware` to make it match the remote version. - -Vivado probably updated the `*.bd` file... Simply run: `git restore ...` to put this file back to a clean state. - -### Add `.gitignore` as needed (private user code only) - -Run `git status` in your private user repo. You should not see compiled output. If git sees changes to the following folders, create a gitignore file so that they are ignored. Note that if the above steps were perfectly followed, you shouldn't have to add any gitignores. - -- `.metadata/` -- `Debug/` -- `Release/` - -## Making Private Repository Portable - -Please read [this document](Create-Private-Repo.md) for instructions on how to further configure your private repository to support expedited cloning. - -## Programming AMDC - -Ensure the AMDC JTAG / UART is plugged into your PC and AMDC main power is supplied. - -### Setup SDK Project Debug Configuration - -1. Right-click on the project you are trying to debug, e.g. `bare` -2. `Debug As` > `Debug Configurations...` -3. Ensure you have a `System Debugger using Debug_bare.elf on Local` launch configuration ready for editing. _If not:_ - 1. Right-click on `Xilinx C/C++ application (System Debugger)` from left pane > `New` - 2. A new panel should appear on the right half of popup -4. Ensure the `Target Setup` tab is open -5. Select `Browse...` for `Bitstream File` - 1. Find the bitstream which Vivado generated (should be at `$REPO_DIR\amdc\amdc.runs\impl_1\amdc_rev*_wrapper.bit`) and click `Open` -7. Check the following boxes: `Reset entire system`, `Program FPGA`, `Run ps7_init`, `Run ps7_post_config` -8. Click `Apply` -9. Click `Close` - -### Running Project on AMDC - -Now, you are ready to start the code on AMDC! - -1. Right-click on application, e.g. `bare` -2. `Debug As` > `Launch on Hardware (System Debugger)` -3. `SDK Log` panel in the GUI will show stream of message as AMDC is programmed - 1. System reset will occur - 2. FPGA will be programmed - 3. Processor will start running your code (must click play button to start it running) -4. NOTE: You only have to do the right-click and debug from the menu the first time -- next time, just click the debug icon from the icon ribbon in the GUI (located to left of play button). - -### Connecting to AMDC over USB-UART - -To interface with the serial terminal on AMDC, your PC needs the appropriate driver: - -- For <= REV D hardware, the UART interface is the `Silicon Labs CP210x USB-UART Bridge` -- For >= REV E hardware, the UART interface is from `FTDI` and should be natively supported by your operating system - -#### For Silicon Labs UART-USB Driver (<= REV D hardware) - -1. Open: https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers -2. Download the right drivers for your platform and install them. -3. Verify the drivers are installed: - 1. Connect a micro USB cable to the "UART" input on AMDC - 2. Check that a `Silicon Labs CP210x USB-UART Bridge` appears as a connected device. - -## Issues - -Getting AMDC to start and run FPGA and C code can be hard. If it isn't working, try repeating the programming steps. Make sure to reset the board by either power cycling AMDC or pushing `RESET` button on AMDC. - -If you are getting compilation errors in the SDK (especially during the linking phase), consider deleting the `amdc_bsp` project and regenerating it -- doing so sometimes resolves common issues. - -NOTE: Pushing the `RESET` button on PCB **is NOT** exactly the same as doing a full power cycle of board. The `RESET` button performs a different type of reset (it keeps around debug configurations, etc). During developement, you may need to perform a full power cycle, while other times, a simple `RESET` button push will work. - -Xilinx tools also have **many** quirks. Good luck getting everything working! diff --git a/docs/Create-Private-Repo.md b/docs/Create-Private-Repo.md deleted file mode 100644 index 2eb44ece..00000000 --- a/docs/Create-Private-Repo.md +++ /dev/null @@ -1,30 +0,0 @@ -# Create Private Repo - -This document explains the steps needed to set-up a private repo for AMDC firmware development. The following steps outline how to create an overarching private repo which can be cloned from git and has all the needed items to flash an AMDC. - -1. Perform all the steps from the [first tutorial](Building-and-Running-Firmware.md), following the "private" code option. -2. Create folders in your repo root (called `$REPO_ROOT` in this doc) for all generated outputs from Vivado and SDK - 1. Create folder `$REPO_ROOT/gen/sdk_gen/` - 2. Create folder `$REPO_ROOT/gen/vivado_gen/` -3. Copy the following from `$REPO_ROOT/AMDC-Firmware/` to `$REPO_ROOT/gen/vivado_gen/` - 1. `hw/` - 2. `amdc/` -4. Copy the following from `$REPO_ROOT/AMDC-Firmware/sdk` to `$REPO_ROOT/gen/sdk_gen/` - 1. `design_1_wrapper_hw_platform_0/` - 2. `amdc_bsp/` - 3. `fsbl/` - 4. `design_1_wrapper.hdf` - -Now, your private repo contains all needed files. Each time you clone it, you will need to follow the following steps to use it. - -## Cloning Private Repo / First-Time Set-Up - -1. Clone your repo from git -2. Make sure the submodules are checked out (`git submodule update --init` etc) -3. Open SDK -4. Select workspace to reside in local user folder (outside of your private AMDC firmware repo). -5. Import the above projects (`design_1_wrapper_hw_platform_0`, `amdc_bsp`, `fsbl`, your `user_code` project in your private repo, not AMDC-Firmware submodule) (open projects from file system) -6. Wait for them to compile... (it will fail due to not knowing about the `AMDC-Firmware` submodule) -7. To fix the errors, redo the steps from the previous tutorial, [starting here](Building-and-Running-Firmware.md#fix-common-code-compilation). This links the `common` AMDC-Firmware submodule code to your private project. - -You can now use your private repo! diff --git a/docs/Create-User-App-Example.md b/docs/Create-User-App-Example.md deleted file mode 100644 index f8d6f0d3..00000000 --- a/docs/Create-User-App-Example.md +++ /dev/null @@ -1,64 +0,0 @@ -# Creating a Custom User App for AMDC - -Guidelines for generating custom user app code for the AMDC - -## Background - -This document gives an overview of generating custom user app code for use with the AMDC. - -AMDC utilizes an RTOS to coordinate system resources for the various tasks that need to be performed. It's important to understand these concepts and can be reviewed [here](https://github.com/Severson-Group/AMDC-Firmware/blob/develop/docs/Firmware-Arch-System.md). - -In short our _application_ has some _task_ (where the work actually gets done) and we, the end user manage the task via serial _commands_. This structure requires 3 files to be constructed in a manner that the AMDC RTOS will recognize. In order to do that we will be basing our custom application off of the Blink application that is included on a clean clone of the AMDC repo, found [here](https://github.com/Severson-Group/AMDC-Firmware/blob/develop/docs/Create-Private-Repo.md). - -## Procedure - -1. Define the application in the compiler symbols list - 1. In the _Project Explorer_ pane right click on _bare_ and select _Properties_ - 2. Expand _C/C++ Build_ and select _Settings_ - 3. Under _ARM v7 gcc compiler_ select _Symbols_ - 4. Click the _+_ icon in the upper right portion of the _Defined Symbols (-D)_ pane - 5. Enter `APP_APPNAME` where `APPNAME` relates to our application purpose or function - 6. Click _OK_, then click _OK_ in the lower right of the Build Properties window -2. Add the application in user_apps.c - 1. In the _Project Explorer_ pane locate user_apps.c under _bare -> usr -> user_apps.c_ - 2. Using `APP_BLINK` as an example copy the `#ifdef APP_BLINK` section and paste it below, replacing `BLINK` with `APPNAME.` Don't worry the folders and files don't exist yet, we'll be adding them shortly. - ```C - #ifdef APP_APPNAME - #include "usr/appName/app_appName.h" - #endif - ``` - 3. Similiarly add a section within the `user_apps_init()` function. - ```C - #ifdef APP_APPNAME - app_appName_init(); - #endif - ``` -3. Create the directory referenced in the _user_apps.c_ file - 1. In the _Project Explorer_ pane right click the _usr_ folder and select _new -> folder_ - 2. name the folder **appName** -4. Create the application source and header files - 1. Using the Blink application as an example copy and paste the _app_blink.c_ and the _app_blink.h_ files into the **appName** folder - 2. Within _app_appName.c_ replace all references to `blink` with `appName` - 1. Delete the `task_vsi_init()` function call - 1. Comment out the `task_appName_init()` function call - 1. In the corresponding header file replace all references to `blink` with `appName` -5. Create the task files, where the work gets done - 1. Using the Blink application as an example copy and paste the _task_blink.c_ and the _task_blink.h_ files into the **appName** folder - 1. Starting with the header file, replace all references to `blink` with `appName` - 1. Note the define `TASK_APPNAME_UPDATES_PER_SEC (#)` is a very useful feature that lets us define our repition rate of our task in Hz. It is not required. - 1. The define `TASK_APPNAME_INTERNAL_USEC (#)` is **required**. This is used by the system counters to know when to trigger our task callback function - 1. Either utilize the `*PER_SEC` define or the `*INTERNAL_USEC` define to trigger your task at the desired rate - 1. Turning to the source file, unless you are using REV_C AMDC hardware remove all sections of code that refer to `USER_CONFIG_HARDWARE_TARGET` in the top of the source file, within `task_appname_deinit()` and within `task_appname_callback()`. Also remove the `#include ...\hardware_targets.h` reference. - 1. `task_appname_init()` is where you will place any initialization code for your task. This function will run once when we start the task. - 1. `task_appname_callback()` will occur at the rate defined by `TASK_APPNAME_INTERNAL_USEC (#)` This is where we will place any code that needs to be run based on the task frequency. -1. Create the command files for control via serial interface - 1. Create the folder **cmd** under the **appName** folder. See blink example file structure - 1. Copy and paste the _cmd_blink.c_ and _cmd_blink.h_ files into the newly created **cmd** folder and rename the files _cmd_appname.c and *.h_ respectively. - 1. Replace all instances of `blink` with `appName` - 1. See the `hello` text parse sections of code for examples of multi level command parsing. - 1. Remove `hello` parse section from `cmd_appName()` and from the `cmd_help[]` struct. -1. Build the project. There should be no build errors, work through them if there are. - -## Summary - -We now have a template file structure for adding custom user applications and task, and the framework to control these applications with the serial interface! diff --git a/docs/Firmware-Arch-Drivers.md b/docs/Firmware-Arch-Drivers.md deleted file mode 100644 index d06d3bd5..00000000 --- a/docs/Firmware-Arch-Drivers.md +++ /dev/null @@ -1,65 +0,0 @@ -# Firmware Architecture - Drivers - -[Firmware Architecture](Firmware-Architecture.md) -- **Drivers** -- [System](Firmware-Arch-System.md) -- [User Applications](Firmware-Arch-UserApps.md) - -## Purpose of Drivers - -Why does the code base use "drivers"? What are these things? - -## Under the Hood - -At a highish level, what does a driver do under the hood? How does it interface with the FPGA? - -## Examples - -Looking at examples is helpful -- here are some common drivers that the user will access frequently. - -### Reading Analog Values from ADC - -There are 16 ADC channels which provide analog input into the AMDC. Each channel supports +/- 10V inputs. To read the analog voltage on these inputs, the `drv/analog.c` driver is used. The user code simply calls a driver function which returns the voltage value. You might expect it to work like the following: Imagine I have 5V applied to the ADC on channel 1. Then, - -``` C -float voltage = analog_read(channel_1); // voltage should now equal 5.0 -``` - -You'll be happy to find that the actual driver code functions almost the same as this. However, if the user asks for a channel which is not valid (i.e., `channel_100`), the driver should indicate this is an error. To do this, the driver function return value is the error indicator. To get the analog voltage value, the user supplies the driver function with a memory address where it should put the analog voltage value. For the same example as above: - -``` C -// Create variables to use -int err; -float voltage; - -// Call driver function -err = analog_read(channel_1, &voltage); - -// Now: -// - 'err' will be the return status of the driver function -// - 'voltage' should equal 5.0 -``` - -We are almost to the final code which you will use in your applications. However, you will find that the function `analog_read(...)` does not exist. Instead, there are two functions: `analog_geti(...)` and `analog_getf(...)`. The first gets the ADC voltage value as an `int` while the second retreives it as a `float`. The `analog_geti(...)` variant is lower-level; it outputs the actual value returned by the ADC. **The `analog_getf(...)` variant should be used most of the time; it returns the actual voltage on the ADC input.** - -Also, you will find that `channel_1` does not exist. Instead, you should use the [enumeration](https://www.geeksforgeeks.org/enumeration-enum-c/) provided in `drv/analog.h`. The mapping between enumeration and ADC channel is nearly 1:1 -- use `ANALOG_IN1` for channel 1, `ANALOG_IN2` for channel 2, etc. - -So, the final version of the code you will use is as follows: -``` C -// Create variables to use -int err; -float voltage; - -// Call driver function -err = analog_getf(ANALOG_IN1, &voltage); - -// Now, you have your output -``` - -### Writing PWM Duty Ratios - -Similar to steps for reading voltages, but with `drv/pwm.c` driver. - -### Reading Encoder Values - -Similar to steps for reading voltages, but with `drv/encoder.c` driver. diff --git a/docs/Firmware-Arch-System.md b/docs/Firmware-Arch-System.md deleted file mode 100644 index 0e511e51..00000000 --- a/docs/Firmware-Arch-System.md +++ /dev/null @@ -1,94 +0,0 @@ -# Firmware Architecture - System - -[Firmware Architecture](Firmware-Architecture.md) -- [Drivers](Firmware-Arch-Drivers.md) -- **System** -- [User Applications](Firmware-Arch-UserApps.md) - -## Background - -The AMDC firmware is designed to include a "system" layer which functions as a **simple** real-time operating system (RTOS). Read about RTOS design principles [on Wikipedia](https://en.wikipedia.org/wiki/Real-time_operating_system) before continuing... - -What makes AMDC's RTOS design **simple**? Why is it used instead of a "real" RTOS (e.g. [FreeRTOS](https://www.freertos.org/))? What features does it expose to the user application? This document aims at answering these questions and more. - -## System Design - -The AMDC *system* layer sits between the *driver* layer and the *user application* layer. Some system resources (such as the serial interface) are managed exclusively by the system layer -- for example, if a user application needs to print a message to the console for debugging, it must request the system to do this action on its behalf (via the `sys/debug.c` module). Other system resources, such as PWM outputs or analog inputs, can be accessed directly by the user application via the driver layer. - -### Tasks - -Tasks are the foundation upon which all system services are built. As described in previous documentation, a task is simply a block of code that runs periodically. Tasks can exist in the user space (used in user applications) or in the system space. - -Example tasks could be: -- `task1` runs at 1Hz and blinks an LED -- `task2` runs at 10kHz and regulates current through an RL load -- `task3` runs at 1kHz and handles serial communciation with UART interface - -Each example task includes a frequency of operation as well as a "high-level" goal. Usually, the task has *state* that is updated each time the task is run. In `task1`, the state might be the current LED status (on/off). Each time `task1` is run, the LED state toggles and the LED is refreshed. - -Notice how each task is independent -- they all run at different frequencies and do different things -- but together, they perform complex actions as a complete system. This is the crux of designing firmware to use a RTOS: splitting code into tasks which work together to solve a complex goal. You will need to do this when building user applications with AMDC. - -#### Example - - - -In the above diagram, three tasks are shown operating over time (referred to as T1, T2, and T3). T3, shown in red, operates every time slice and consumes nearly half the quantum. T2, shown in green, operates at half the frequency of T3 and consumes less computation time. Finally, T1, shown in blue, runs at a third the rate of T3 and only for little bursts of time. - -Notice how the system is idle during the solid red regions on the top bar. Also, notice the inherit jitter in T1 -- the scheduler runs it during the correct time slices, but depending on T2, T1's periodicity changes slightly. During time slice 3, T1 occurs earlier in the time slice than in time slice 0 or 6. Jitter (or lack of) can be important in some applications and the developer must be aware of the system behavior. - -#### Cooperation Between Tasks - -The system scheduler (`sys/scheduler.c`) is responsible for running the registered tasks of the system. Tasks are **non-preemptable** by design. This means that once a task starts, it runs until it *yields* the processor -- it cannot be interrupted by the system. Imagine a task which has a lot of work to do. It gets scheduled to run by the scheduler and starts execution. **It then has complete control of the entire system.** It can choose to run as long as it wants. Only when it stops doing work and *yields* the processor does the scheduler regain control. At this point, the scheduler chooses another task to run and the cycle repeats. - -Why is it important to understand that tasks are **non-preemptable**? Well, because of this fact, the scheduler is therefore designed for **cooperative** tasks (one would say that AMDC uses "cooperative scheduling"). The scheduler *relies* on the assumption that each task can be trusted -- no tasks are malicous and will take over the system by not yielding the processor. It is up to the developer to ensure this is true, otherwise the system will not operate correctly. - -In practice, how does one create a cooperative task? This boils down to keeping tasks short. Only do a small amount of work, then stop and yield the processor. The maximum frequency of tasks (typically 10kHz) determines the total amount of time available for tasks during a time slice. At 10kHz scheduler frequency, the elementary time quantum is 100us. This means that all tasks registered with the scheduler must complete within 100us. Note that 100us is the *combined* time -- if there are 10 tasks that need to run at 10kHz, the total sum of the run time for each task must be less than 100us otherwise the scheduler time quantum will be overrrun. This is bad and should be avoided. - -What if a task must perform complex actions that take too long? If a task must do a lot of work, the developer must break up the work into small chunks, then perform each chunk of work during its own time slice. Breaking up work into small chunks is an advanced topic and will be covered in later documentation. - -Read more about cooperative scheduling of tasks [on Wikipedia](https://en.wikipedia.org/wiki/Cooperative_multitasking). - -### Commands - -Commands provide interactive operation of AMDC with the outside world. While the hardware I/O allows AMDC to change state based on the physical world, commands are designed for higher-level actions (i.e., "turn off LED" or "spin motor to 100RPM"). Commands are completely optional in user applications and are triggered by user input on the serial terminal. - -Example commands: -- `led toggle red` -- Toggle the red LED -- `motion rpm 100` -- Command a rotational speed of 100 RPM to a motion controller -- `motion bw 10` -- Set bandwidth of motion controller to 10 Hz -- `hw anlg read 0` -- Read hardware analog channel 0 voltage and display to terminal -- `hw pwm duty 2 50` -- Set PWM output 2 to duty ratio of 50% - -Notice how commands are created by characters seperated by white space. The first chunk of characters is defined as the *base command*. For the above examples, base commands are `led`, `motion`, and `hw`. Subsequent sequences of characters seperated by white space are called *arguments* or *subcommands*. - -#### Handling Commands - -The commands system module (`sys/commands.c`) is responsible for parsing the characters from the terminal and calling the appropriate *command handler*. A command handler processes a single base command -- this includes all subcommands and arguments. Each base command is registered with the system during initialization and start-up so that the system knows it exists. All commands have a *help* message which the system will display if the user types "help". - -The command handler signature follows standard C convention for the main function of a program called from the command line: - -```C -int main(int argc, char **argv); -``` - -`argc` contains the number of arguments passed to the command. For example, if the user typed `cmd arg1 arg2`, `argc` would contain 3. - -`argv` contains an array of character strings for each argument. For above example: -- `argv[0]` => `"cmd"` -- `argv[1]` => `"arg1"` -- `argv[2]` => `"arg2"` - -#### Command Flow - -When the user types a command into the terminal, a complex set of operations is set in motion (see implementation in `sys/commands.c`). Below is a diagram of the flow of each character the user types. When developing user applications that include commands, the developer does not need to worry about the following information, as they simply implement the commmand handler (shown in green below). For completeness, the following discussion is presented as explanation for how the user-supplied command handler is (eventually) called. - - - -What happens when a character is entered into the terminal? The user terminal reads the input character and sends it to the driver on their PC which talks with the USB-UART device on AMDC. The character is then sent over the physical medium to the AMDC hardware. The UART hardware peripherial buffers incoming characters in a small FIFO (first-in, first-out) structure (referred to as simply FIFO). - -At this point, the system firmware begins. The UART FIFO is drained and the incoming characters are copied into a much larger queue structure in system memory (shown in orange above). A task then runs periodically to parse these characters and form pending commands which get placed in another system level FIFO. Yet another task then runs each pending command in its own scheduler time slice, which calls the user defined command handler. The `argv` argument of the command handler now points to characters which reside in the orange incoming characters FIFO. - -These layers of communication, FIFOs, and queues are used to provide hard guarantees about system performance. They enforce the following: -- A single valid command is run during each time slice -- Commands have a limited number of arguments -- Each argument has a limited length diff --git a/docs/Firmware-Arch-UserApps.md b/docs/Firmware-Arch-UserApps.md deleted file mode 100644 index 17b03240..00000000 --- a/docs/Firmware-Arch-UserApps.md +++ /dev/null @@ -1,10 +0,0 @@ -# Firmware Architecture - User Applications - -[Firmware Architecture](Firmware-Architecture.md) -- [Drivers](Firmware-Arch-Drivers.md) -- [System](Firmware-Arch-System.md) -- **User Applications** - -TODO: write more information here about user apps. - -For now, check out [this document](./Create-User-App-Example.md) about creating a new user app in the `bare` project via the SDK. diff --git a/docs/Firmware-Architecture.md b/docs/Firmware-Architecture.md deleted file mode 100644 index 9f2bab69..00000000 --- a/docs/Firmware-Architecture.md +++ /dev/null @@ -1,75 +0,0 @@ -# Firmware Architecture - -**Firmware Architecture** -- [Drivers](Firmware-Arch-Drivers.md) -- [System](Firmware-Arch-System.md) -- [User Applications](Firmware-Arch-UserApps.md) - -## Motivation - -This document outlines the AMDC firmware architecture. By understanding the high-level concepts, it will make reading the code and writing custom applications much easier. Care has been taken to ensure that users will not need to "reinvent the wheel" -- common systems like command processing, task management, and logging have been built into the architecture. - - -## Layers of Abstraction - -A good software system has many layers of abstraction. The client of one subsystem does not need to know how that subsystem works interally -- she/he simply uses the interface provided and expects that it works as specified. The AMDC firmware is structured in this manner. - - - - -## Hardware - -All firmware runs on the AMDC controller board, the [PicoZed](http://zedboard.org/product/picozed), which in turn runs on a [Xilinx Zynq-7000 SoC](https://www.xilinx.com/products/silicon-devices/soc/zynq-7000.html). This SoC is very powerful -- it contains dual core ARM Cortex-A9 processors along with tightly integrated FPGA fabric. - -### ARM Cortex-A9 Processors - -The Cortex-A9 processors (referred to as digital signal processors, DSPs) are responsible for running the actual C code. Most user firmware development of control algorithms will be in C and run on the DSP. - -### FPGA - -The tightly integrated FPGA exists "next" to the DSPs, and acts like a huge custom peripherial for the DSPs. Users can develop any custom digital circuitry and access it from the C code. This is very powerful and can be used to implement almost anything: -- Fast, high-bandwidth I/O to PCB level devices -- Accelerators which assist firmware by offloading computation to hardware - - -## Drivers - -All firmware drivers are located in the `drv` directory. These modules are responsible for commuicating with hardware devices. Driver modules provide a layer of abstraction between system / user code and hardware resources. Users will need to access hardware resources regulary (i.e. to update PWM duty ratios or read in an analog value). By using the provided drivers, this interaction is made easy. - -Most of the system hardware peripherials are located in the FPGA and are custom for the AMDC PCB. This includes the PWM generation, interface to the ADCs for analog measurement, GPIOs, encoder, etc. To control these FPGA modules, registers are read / written from C. The C firmware must know the addresses of these registers to access the data they want. Instead of putting this burden on each user who is trying to use AMDC, the drivers take care of this. The user can use "high-level" commands with the driver -- i.e., "set the PWM duty ratio to 50% for output 1". - -As an example, consider trying to use an FPGA timer. The timers can be configured to trigger interrupts from the FPGA. The `drv/timer.c` driver abstracts away the fairly complex task of initializing the FPGA hardware and setting up register values. Instead, the user can essentially command: "set up timer 1 to trigger interrupt at 10kHz" and it automagically happens. :) - -[Read more about the driver layer...](Firmware-Arch-Drivers.md) - -## System - -All firmware system code is located in the `sys` directory. These modules are responsible for "system" level subsystems. The AMDC firmware system code is designed to behave similar to a [Real-Time Operating System (RTOS)](https://en.wikipedia.org/wiki/Real-time_operating_system), but without the added complexity of a full RTOS. - -[Read more about the system layer...](Firmware-Arch-System.md) - -## User Apps - -All user apps are written in C and exist in the `usr` directory. These apps are built on top of all other system functionality (the system modules, drivers, and hardware). These user apps interact with the system code for various things: register tasks, register commands, etc. The user apps can also interact with the system drivers to interface with hardware resources (i.e., PWM outputs, analog inputs, etc). - -[Read more about the user application layer...](Firmware-Arch-UserApps.md) - -To understand how to create an application for AMDC, you must fully grasp the following concepts: *tasks* and *commands*. - -### Tasks - -The AMDC firmware is mainly *task based*. These tasks are repeatedly executed at user-specified intervals (i.e., 1Hz, 500Hz, 10kHz, etc). You can think of a task as simply a block of code that runs periodically. These tasks can form the backbone of a user control algorithm. For example, imagine a PID controller. This code must be executed periodically to update its state. This would fit naturally into a **task** -- the user can configure the system to run the controller task at a periodic interval so that the state updates. - -[Read more about tasks...](Firmware-Arch-System.md#tasks) - -### Commands - -To interact with the firmware which is running on AMDC, a command-line interface is used. The user types commands into the terminal and the firmware responds and performs the desired actions. There are several built-in commands on AMDC, for example, the `hw` command allows the user to access various hardware systems like PWM and analog. - -However, each user application will most likely require its own commands in addition to the default system commands. For example, if an application is controlling a motor, having a command to set the desired output shaft speed would be helpful. The system `commands.c` module exists for this purpose -- the user application simply registers their own command with the system. The user does not need to understand how the incoming characters are parsed and handled -- the system will call the user command handler function if their command has been typed in. - -[Read more about commands...](Firmware-Arch-System.md#commands) - -## Examples - -To fully grasp the AMDC firmware architecture, examples are provided which concretely show the ideas presented above. See the example application: [`blink`](../sdk/bare/user/usr/blink/). diff --git a/docs/Firmware-Logging.md b/docs/Firmware-Logging.md deleted file mode 100644 index 13d86ea0..00000000 --- a/docs/Firmware-Logging.md +++ /dev/null @@ -1,455 +0,0 @@ -# AMDC Variable Logging - -This document describes and documents the "logging" functionality that is designed into the AMDC system firmware. At the highest level, logging simply means recording variables over time from inside the firmware and retreiving the sampled values. This functionality is so fundamental to working with embedded systems that it is included directly from the main `AMDC-Firmware` code distribution -- the user does not have to reinvent the wheel. - -## Contents -- [Introduction](#introduction) -- [General Flow](#general-flow) -- [Internal Workings](#internal-workings) -- [C-Code Modifications](#c-code-modifications) -- [Terminal Interface](#terminal-interface) -- [Python Interface](#python-interface) - - [Function Reference](#function-reference) - - [Copy-Paste Example](#copy-paste-example) - - -## Introduction - -The ability to log and extract variables of interest out of the AMDC is a critical feature needed for debugging, testing, and general data recording. Because of this, the AMDC has logging capabilities built into the firmware and can record up to 32 variables at one time. The intent of this document is to show a user how to implement logging in their code. - -There are two interfaces that can be used for logging: 1) the standard serial terminal interface that is typically used with user commands (logging can be thought of as an application that can be included in your project) and 2) a Python interface that is built on top of and wraps the serial interface. It is highly recommended that users use the Python interface as it provides certain convenience methods and abstractions that make logging much more intuitive and less error prone. - -## General Flow - -The general flow for logging data in the AMDC is simple and uses the procedure shown below. Note that steps 2-5 are done through the Python interface: - -1. Set up user C-code for logging (one time operation -- this is sometimes called "instrumenting the code") -2. Register variable(s) to log -3. Start logging -4. Stop logging -5. Dump the collected data - -## Internal Workings - -To further help the user understand the functionality of the logging engine, a description of its inner workings is provided here. Note that this is only for background information; the user does not need to update or change the embedded logging engine (`sys/log.c`). - -For each C-code variable which should be logged, e.g. `LOG_x`, a slot is allocated within the logging engine. This slot contains metadata as well as a large memory array. When logging starts, the value of the logged variable (e.g. `LOG_x`) is copied into the memory buffer at the specified sampling interval. Once logging is done, this large array of samples can be transfered from the AMDC to the host via the command-line interface. - -### Specifications - -The logging engine, by default, can record 32 different variables at one time. Each variable log has a maximum sample depth of 100k samples. Once this is full, the logging system automatically stops writing data (even if logging is still enabled). The logging engine *does not* use a circular buffer approach. - -These defaults (32 slots at 100k sample depth) can be configured by the user. Note that the maximum memory usage is limited, so users must keep the product of these the same. For example, the user could change the settings to be 128 variables at 25k sample depth. - -## C-Code Modifications - -The firmware has been designed specifically to limit the amount of changes users have to make to their C-code to log variables of interest. The only modifications that users need to make to their C-code are as follows: - -1. Enable the logging feature using the config file `usr/user_config.h`. This is located in the `usr/` folder of your private C code. Set the following define variable to `1`: - -```C -#define USER_CONFIG_ENABLE_LOGGING (1) -``` - -Note that it is set to 0 (logging disabled) by default. - -2. For every variable that you want to log within your C code, create a new global variable with the same name prepended by `LOG_` (note that it is case sensitive). For example, if you have a variable `foo` in your code that you would like to log, create a new global variable of the same type called `LOG_foo`. Please note that the total logging variable name length is limited to 16 characters (including the `LOG_` prefix). - -3. Update all global logging variables wherever desired by assigning the local variable to the global variable (e.g. `LOG_foo = foo;`) - -### Example - -The following example illustrates one possible use case: - -We have a typedef called `Currents_t` that is a struct containing measured currents from each of the three inverter phases. This variable is then updated by the generic `read_currents` function. You could imagine this function is reading in the three current sensors from an inverter. **We wish to log the three phase currents.** To do this, we use the two steps listed above. - -First, we create global variables for each of the three currents that we care about. Then, in the callback function, we update the global current variables to equal the measured currents that we care about tracking. Note that in this example we update the global variables within the callback, but you can update them at any point in your code. For example, we could have updated the global variables inside of the `read_currents()` function - -```C -double LOG_Ia = 0.0; -double LOG_Ib = 0.0; -double LOG_Ic = 0.0; - -typedef struct Currents_t { - double Ia; - double Ib; - double Ic; -} Currents_t; - -static Currents_t Iabc = { 0.0, 0.0, 0.0 }; - -void example_callback_func(void) -{ - // Read currents from sensors - read_currents(&Iabc); - - // Update variables that are being recorded by logging application - LOG_Ia = Iabc.Ia; - LOG_Ib = Iabc.Ib; - LOG_Ic = Iabc.Ic; -} -``` - -There are no other steps needed in the embedded code running on the AMDC for logging. The only requirement is that you have exposed a global variable and updated it. The rest of the logging system is handling by the system code. - -## Terminal Interface - -The terminal interface operates the same as any other user application (as shown below). It is highly recommended, however, that the user does not use the terminal interface directly, as it is difficult to use and requires a lot of book keeping. The following documentation will describe each of terminal commands, but is primarily just for completeness of the documentation. - -The following is the `help` output for an example user application. The low-level `log` commands are highlighted. - - - -The logging system has the following commands: - -1. `reg` -- registers a new variable for logging - > **Required Arguments** - > - `log_var_idx` -- the index that you want the variable to be stored in (must be 0-31). The command will fail if a variable is already registered in the requested slot. - > - `name` -- name of the variable that you are logging (example: `LOG_foo`) - > - `memory_addr` -- global memory address of the variable you are logging in decimal format. The reason global variables are created for logging is because their address remains constant at runtime. The memory address can be found in "mapfile.txt" in a hexadecimal format, which is located in the "Debug" folder of the users private c code. After locating the variable's address, you must convert it from hexadecimal to decimal before entering it in the terminal. - > - `samples_per_sec` -- the sample rate in samples per second that you wish to record the variable at. Note that not all variables have to have the same sample rate. This generally can range from 1 to 10kHz. - > - `type` -- data type of the variable being logged. Valid types are: `double`, `float`, `int` - -2. `unreg` -- unregisters a variable that you no longer care to log - > **Required Arguments** - > - log_var_idx -- the index of the variable that you want to unregister (must be 0-31). - -3. `start` -- starts recording data - -4. `stop` -- stops recording data - -5. `dump` -- dumps all of the recorded data of a slot out to the serial terminal - > **Required Arguments** - > - `bin` or `text` -- One of the preceding flags must be set. If `bin` is used, the data will be dumped to the serial terminal in binary format. If `text` is used, the data will be dumped to the serial terminal in human readable text format. Using `bin` is much faster. - > - `log_var_idx` -- index of the variable that you wish to dump (must be 0-31) - -6. `empty` -- resets the specified logging slot (calling `dump` after `empty` on the same slot will result in no data being output) - > **Required Arguments** - > - `log_var_idx` -- index of the variable you wish to reset - -7. `info` -- prints information about the logging system (registered slots, samples, etc) to the serial terminal - -## Python Interface - -Before you can use the Python interface, you must modify your C code according to the C-Code Modifications [section](#c-code-modifications). - -Note that in the text that follows, `REPO_DIR` is an alias for the file path to where your repository is located. `REPO_DIR` contains the `AMDC-Firmware` submodule as well as a the folder containing your user C-code. - -The Python interface is built on top of the serial terminal logging interface in the sense that it simply enters commands at the serial terminal for you. What makes this so advantageous, however, is that Python can take care of all of the book keeping for you. The Python interface offers the following advantages: - -- Remembering which variables are being logged, which variables are in which slots, and which slots are still available -- Automatically determines the memory addresses of each variable and converts them to the correct format -- Reads dumped data off of the serial terminal and converts it to a format that can be saved to a `.csv` file -- Other convenience functions that make logging much easier - -The following steps describe how to use the Python interface. (Note that a full example that users can copy and paste is included at the [end](#copy-paste-example)) - -### 1. Import needed modules - -To use logging in Python, you must `import` the `AMDC` and `AMDC_Logger` modules from the scipts folder of the AMDC-Firmware. There are two main classes that you need to be concerned with: - -1. `AMDC`: class that is found in the `AMDC` module. Responsible for communicating with the AMDC over serial terminal -2. `AMDC_Logger`: class that is found in the `AMDC_Logger` module. Responsible for sending logging commands to the AMDC and book keeping - -The top of your Python script should look like the following: - -```Python -import sys -scripts_folder = r'REPO_DIR\AMDC-Firmware\scripts' -sys.path.append(scripts_folder) - -from AMDC import AMDC -from AMDC_Logger import AMDC_Logger, find_mapfile -``` - -Adding the location of the scripts folder to the `sys.path` variable allows Python to find the `AMDC` and `AMDC_Logger` modules to import them. - -After importing the modules, perform the following steps: - -### 2. Instantiate an `AMDC` object and connect it to the AMDC: - -```Python -amdc = AMDC(port = 'COM4') -amdc.connect() #opens up serial communication -``` - -Note that you may need to change the port number (i.e. `COM4` --> `COM3`, etc.) depending on which port the AMDC is communicating to your computer through. You can determine this by first connecting to the AMDC through the terminal and noting which port it tries to automatically connect with. - -### 3. Instantiate an `AMDC_Logger` object: - -```Python -mapfile_path = find_mapfile(REPO_DIR) -logger = AMDC_Logger(AMDC = amdc, mapfile = mapfile_path) -``` - -The `AMDC_Logger` object requires two inputs on instantiation: an `AMDC` object (created in step 2), and a file path to where the mapfile is located. You can manually locate and specifiy the location of `mapfile.txt` or you can use the convenience function `find_mapfile()` which takes in the base path of the repository and locates and returns the path to the mapfile. - -### 4. Synchronize logger with AMDC: - -```Python -logger.sync() -``` - -This step isn't required but is recommended. It reads the current state of logging in the AMDC and synchronizes Python to that state. It's useful for if you restart your Python session while the AMDC is still on. If you don't do this and variables are are set up for logging in the AMDC, the internal state of Python's book keeping and the AMDC won't align and you'll get unexpected behavior. - -### 5. Register variables of interest: - -There are several ways to register variables for logging. One way is as follows: - -```Python -logger.register('LOG_foo', samples_per_sec = 1000, var_type = 'double') -``` - -Note that register has default arguments of `samples_per_sec = 1000` and `var_type = 'double'` so the preceding line could also be accomplished as follows: - -```Python -logger.register('LOG_foo') -``` - -If you have multiple variables that you wish to register with the same type and sample rate you can register them all at the same time. the `AMDC_Logger` class is also smart and sanitizes the input variables so you don't have to prepend `LOG_` to each variable if you don't want. The following snippets of code all accomplish the same task. - -```Python -logger.register('LOG_foo LOG_bar LOG_baz') # variable names in one string seperated by white space -logger.register('foo bar baz') # one string with no LOG_ (this option is probably the fastest/easiest) -logger.register(['foo', 'bar', 'baz']) #list of variable names no -logger.register(('foo', 'bar', 'baz')) #tuple of variable names -``` - -There is also a convenient `auto_register()` function that can be used to search your user code for variables of the form `LOG_*` and register them for you automatically. You just give the file path to your app's c code as follows: - -```Python -logger.auto_register(path_to_user_app) -``` - -if you want to check to see which variables the auto register function will register before calling it, you can call the `auto_find_vars()` function as follows: - -```Python -log_vars, log_types = auto_find_vars(path_to_user_app) -``` - -where `log_vars` is a list containing all of the variables found in the user c code and `log_types` is a list containing the corresponding variable types. - -### 6. Clear logged variables: - -```Python -logger.clear_all() -``` - -Calling the `clear_all()` method resets all of the internal indices of the logger so that you don't receive old logged data. You can kind of think of it as clearing the log. If you just wish to clear a single variable for some reason you can call the `clear()` method and pass in the name of the variable you wish to clear. NOTE: clearing variables does not unregister them from logging. - -```Python -logger.clear('foo') #clear single variable -``` - -### 7. Start logging: - -```Python -logger.start() -``` - -### 8. Stop logging: - -```Python -logger.stop() -``` - -Typically, you will want to record an event or to record data for a set amount of time. Because of this, it is common to import the `time` module and to use the `sleep()` function which expects a delay time in seconds. The following example illustrates a common use case: - -```Python -logger.clear_all() -logger.start() - -do_something() -time.sleep(3) # Record data for 3 seconds - -logger.stop() -``` - -### 9. Dump data: - -After collecting data, you will want to access that data. You do that as follows: - -```Python -data = logger.dump() -``` - -The output of the `dump()` method is a `pandas.DataFrame` object. `pandas` is a popular data science library in Python and a `DataFrame` is the primary object that `pandas` works with. You can kind of think of a `DataFrame` like an Excel spreadsheet. The columns of the `DataFrame` correspond to each logged variable and the index of the dataframe is time. - -The `dump()` function is powerful and has a lot of optional arguments. By default, `dump()` will dump out all logged variables. This can be time consuming though, so if you want, you can specifiy a subset of variables to dump as follows: - -```Python -data = logger.dump(log_vars = 'foo bar') -``` - -You can also specify a file path and `dump()` will automatically save your data to a `.csv` file. This is nice to make sure your data is persistent between experiments. By default, the `dump()` function appends a timestamp to your file name so that you can't accidently overwrite data that you've collected. - -```Python -data = logger.dump(log_vars = 'foo bar', file = 'my_data.csv') -``` - -Sometimes it is nice to add notes to a specific set of data. You can do this by adding the additional optional parameter `comment` to the `dump()` function. - -```Python -data = logger.dump(log_vars = 'foo bar', file = 'my_data.csv', comment = 'the motor appeared to run smooth') -``` - -### Example - -Now that your data is in a `DataFrame` you can post-process it however you wish. As motivation for why `DataFrame` is powerful for logging and debugging, consider the following example. - -Imagine we have recorded position data from `x`, `y`, and `z` displacement sensors as well as measured three phase currents `Ia`, `Ib`, and `Ic`. We can extract all of the data into a single dataframe and save the data as follows: - -```Python -data = logger.dump(file = 'sensed_values.csv') -``` - -Now we can make a plot of only the position data in a single line by calling the `plot()` method on the `DataFrame` while indexing into the displacement data: - -```Python -data['x y z'.split()].plot() -``` - -Note that the above single line is equivalent to the following: - -```Python -pos_vars = ['x', 'y', 'z'] -pos_data = data[pos_vars] #index into dataframe to get position data columns (returns new dataframe) -pos_data.plot() -``` - -This is just one example of how quick `pandas` can make visualizing our logged data for debugging and quick inspection. - -### Miscellaneous Log Functions - -#### Log for set duration - -```Python -logger.log(duration = 0.5) # Record data for about half second -``` - -The above is exactly equivalent to - -```Python -logger.start() -time.sleep(0.5) -logger.stop() -``` - -#### Unregister Variables - -Perhaps for some reason you want to unregister some variables, that can be done easily using the `unregister()` method as follows: - -```Python -logger.unregister('foo bar') -``` - -Again the input can take many forms, the following would also work: - -```Python -logger.unregister('LOG_foo LOG_bar') -logger.unregister(['LOG_foo', LOG_bar']) -logger.unregister(['foo', 'bar']) -... -``` - -If for some reason you want to unregister all variables, you can do that as the following: - -```Python -logger.unregister_all() -``` - -#### Load saved data - -At some point you'll probably want to load in an old run of data. You can do this using the `load()` method as follows: - -```Python -data = logger.load('old_data_file.csv') -``` - -This will load your old data run into a `pandas` `DataFrame`. The load function is just a thin wrapper around the `pandas` `read_csv()` method and the above line of code is equivalent to: - -```Python -data = pd.read_csv('old_data_file.csv', comment = '#', index_col = 't') -``` - -loading the data this way sets time to be the index of the `DataFrame` and ignores any comments you may have stored with the data. - -### Function Reference - -The following are methods available in the `AMDC_Logger` class: - -#### Registering / Unregistering - -- `register(log_vars, samples_per_sec = 1000, var_type = 'double')` -- `auto_register(root, samples_per_sec = 1000)` -- `unregister(log_vars, send_cmd = True)` -- `unregister_all()` -- `auto_find_vars(root)` - -#### Clear Log Slots - -- `clear(var)` -- `clear_all()` - -#### Log Status - -- `info()` -- `sync()` - -#### Start / Stop - -- `start()` -- `stop()` -- `log(duration = 0.25)` - -#### Dump Data / Load - -- `dump(log_vars = None, file = None, comment = '', timestamp = True, timestamp_fmt = '%Y-%m-%d_H%H-M%M-S%S', how = 'binary', max_tries = 4, print_output = True)` -- `load(file)` - -### Copy-Paste Example - -The following Python script example shows the full flow of logging on the AMDC. Users can copy and paste this script to get started. - -```Python -import time -import pathlib as pl -import sys -repo_dir = '' # CHANGE THIS TO YOUR REPO DIRECTORY -repo_dir = pl.Path(repo_dir) -scripts_folder = repo_dir / 'AMDC-Firmware' / 'scripts' -sys.path.append(str(scripts_folder)) - -from AMDC import AMDC -from AMDC_Logger import AMDC_Logger, find_mapfile - -#################### SETUP LOGGER #################### -amdc = AMDC(port = 'COM4') # MIGHT HAVE TO CHANGE THE PORT NUMBER -amdc.connect() #opens up serial communication - -mapfile_path = find_mapfile(repo_dir) -logger = AMDC_Logger(AMDC = amdc, mapfile = mapfile_path) -logger.sync() - -#################### REGISTER VARIABLES #################### -user_app_c_code_path = '' # SET THIS TO PATH OF YOUR USER APPLICATION CODE -logger.auto_register(user_app_c_code_path) - -# View which variables are logged -logger.info() - -#################### COLLECT DATA #################### -# Clear the logger, then record data -logger.clear_all() -logger.start() - -# DATA IS BEING RECORDED -time.sleep(3) - -logger.stop() - -#################### DUMP DATA AND PLOT #################### -data = logger.dump(file = 'test_data.csv') -data.plot() -``` - diff --git a/docs/Firmware-Upgrades-Per-Hardware-Target.md b/docs/Firmware-Upgrades-Per-Hardware-Target.md deleted file mode 100644 index 65a205b1..00000000 --- a/docs/Firmware-Upgrades-Per-Hardware-Target.md +++ /dev/null @@ -1,44 +0,0 @@ -# Firmware Upgrades Per Hardware Target - -This document should be referenced when changing hardware targets. Each revision of the AMDC hardware is slightly different, so the user code must be modified. Note that the hardware targets are almost interchangable, minus the small changes listed below. - -Most changes occur automatically in the core firmware by simplying `define`ing the appropriate hardware target in the `usr/user_config.h` file. - -For example, to target AMDC REV D hardware, make sure the following code exists in your `usr/user_config.h` file: - -```C -// Set hardware target to AMDC REV D -#define USER_CONFIG_HARDWARE_TARGET AMDC_REV_D -``` - -The supported targets are: `AMDC_REV_C` and `AMDC_REV_D` - -## AMDC REV C - -This is the baseline supported hardware target. Note that this hardware is considered old and is minimally supported. Please upgrade to more recent hardware versions if possible. - -Compile code with `AMDC_REV_C` as the hardware target. - -Notable unique hardware specs include 16x analog inputs and one RGB LED output. To configure the LED, use the `drv/io.c` module. - -## AMDC REV D - -This is the latest hardware target and is actively supported. - -Compile code with `AMDC_REV_D` as the hardware target. - -### Changelog - -- Only 8x analog inputs available. -> When using the `enum` values from `drv/analog.h`, the `ANALOG_IN9..16` simply won't be defined, resulting in a compiler error. - -- Four RGB LEDs available. -> Control these using the `drv/led.h` module. - -- PWM general status lines: A, B, C, D. -> Each power stack port has four generic status lines. The directionality is configurable by jumpers on the PCB, but is shared between ports. - -- SensorCard platform motherboard support available. -> The motherboard is not supported on the REV C hardware target. It is only available on REV D hardware. -> -> It is enabled using the `usr/user_config.h` system: define `USER_CONFIG_ENABLE_MOTHERBOARD_SUPPORT` to `1` to enable. This will configure the FPGA driver automatically and add a basic command to the CLI to exercise the motherboard interface manually. diff --git a/docs/Flashing-AMDC.md b/docs/Flashing-AMDC.md deleted file mode 100644 index 38784d28..00000000 --- a/docs/Flashing-AMDC.md +++ /dev/null @@ -1,82 +0,0 @@ -# Flashing AMDC - -During development, JTAG will be the primary interface for programming and debugging the AMDC firmware. Once code is stable, an image can be programed into the AMDC non-volatile memory (NVM). This allows board to boot itself when powered up. - -The following steps outline how to create a boot image and flash the AMDC NVM. - -## Generating boot image file - -The PicoZed system-on-module (SoM) on AMDC includes a flash memory device which stores the boot image for start-up. We need to first generate the appropriate image which will be loaded into this memory. - -### Ensure you have `fsbl` project in SDK - -Xilinx provides a First-Stage Bootloader application project which we will use to create our boot image. If you do not have the `fsbl` project in SDK: - -1. `File` > `New` > `Application Project` -2. Name it `fsbl` -3. Ensure `Board Support Package:` is `Use existing: amdc_bsp` -4. Click `Next >` -5. Select `Zynq FSBL` template -6. Click `Finish` -7. Your new `fsbl` project will be imported and build. It should not have issues. - -### Generate boot image - -1. Right click on the `fsbl` project directory in SDK -2. Select `Create Boot Image` - -![Dropdown menu from First Stage Bootloader (FSBL) project](images/flashing/img1.png) - -4. Ensure popup menu settings look like the following. - -Explaination of settings: `.MCS` is the file format which is supported for QSPI flashing. The list of three items for "Boot image partitions" must always be the following in this order: fsbl.elf, FPGA bitstream .bit file, your user *.elf file. - -![Popup menu settings](images/flashing/img2.png) - -5. Click `Create Image` -6. If it warns that another file already exists, click `OK` -7. It will take a second to create the boot image - -## Programming the flash memory device - -After generating the boot image `*.MCS`file, we need to program the flash device for persistent storage. - -1. Ensure you powered up the board in the correct boot mode. This is selected via the switches on the PicoZed PCB. See image below for switch positions for JTAG mode (what you want). - -![PicoZed switch positions for JTAG mode](images/flashing/sw-jtag.jpg) - -2. Click `Xilinx Tools` from the main dropdown in the SDK -3. Click `Program Flash` - -![SDK menu button to click](images/flashing/img3.png) - -4. Ensure popup window looks like the following: - -![Program Flash Memory popup window settings](images/flashing/img4.png) - -5. Click `Program` -6. It will start flashing the board. This will take ~3 minutes... - -### Known Issues - -Sometimes, when you try to flash the device (i.e. set 5-6 above), the `fsbl` main function will appear in SDK because a breakpoint is reached. This occurs because the SDK debugger is still attached to the AMDC. To fix this, disconnect the processor from the SDK Hardware Manager window. - -## Configure PicoZed to boot from flash - -Now the boot image has been loaded onto the PicoZed flash device. We need to configure PicoZed to use it when booting. - -1. Set PicoZed switch positions to the flash boot mode (see image below). - -![PicoZed switch positions for flash boot mode](images/flashing/sw-flash.jpg) - -## Test - -AMDC should now be programed and ready to go! Time to test. - -1. Power cycle the board. -2. Ensure that `FPGA DONE` yellow LED comes on after ~1 second of powering up -3. If so, you are good to go! - -## Notes - -NOTE: you will need to put the PicoZed switches back to JTAG mode if you would like to continue development using JTAG. diff --git a/docs/Installing-Xilinx-Tools.md b/docs/Installing-Xilinx-Tools.md deleted file mode 100644 index ef1491c7..00000000 --- a/docs/Installing-Xilinx-Tools.md +++ /dev/null @@ -1,65 +0,0 @@ -# Installing Xilinx Tools - -This document describes how to install the required Xilinx tools for building, compiling, debugging, and flashing the AMDC firmware. In order to use the AMDC, you will need to install the following programs: -1. Xilinx Vivado -2. Xilinx SDK - -Xilinx regularly updates the Vivado suite of tools. They release main updates in the format of year and version numbering YYYY.V (i.e. 2017.2). These upgrades are sometimes backwards compatible, sometimes not. Vivado 2019.1 is the last version of the Xilinx tools which uses the SDK environment -- 2019.2 and onward use the new Xilinx Vitis environment. We will be using the SDK, so we will download and install Vivado 2019.1. - -**Warning:** The Vivado suite of tools takes a lot of space on your PC! Please ensure you have ~35GB of local free space before proceeding! You will also need a fast internet connection to download the required files! - -## Installing Vivado 2019.1 - -*This tutorial will assume you are installing Vivado locally on your personal **Windows** PC.* - -Ensure you have no Xilinx tools previously installed on your PC (i.e. make sure you don't have `C:\Xilinx` folder). If you do, uninstall them now. - -### Downloading installation executable - -1. Go to the [Xilinx Downloads page](https://www.xilinx.com/support/download/index.html/content/xilinx/en/downloadNav/vivado-design-tools/archive.html) expand 2019.1. - -2. Scroll down to the "Vivado Design Suite - HLx Editions 2019.1 Full Product Installation" section for 2019.1 - -3. It is strongly recommended to use the web installer (you will need to download ~8GB of data). Click on the link for: "Vivado HLx 2019.1: WebPACK and Editions - Windows Self Extracting Web Installer" - -4. This will make you create a Xilinx account. Do this and continue. - -5. Fill out the required "Name and Address Verification" page and click Download. - -### Installing - -6. Run the installer. Wait for it to extract itself... Once it runs, allow it to change your hard drive. - -7. When the installer opens, it will prompt you to get the latest software version. Decline by clicking `Continue`. - -8. Click `Next >`. - -9. Enter your Xilinx account login info you created previously and click `Next >`. - -10. Check all boxes to agree to terms and click `Next >`. - -11. Select "Vivado HL System Edition" and click `Next >`. - -12. *Recommended but optional to reduce disk space:* Under `Devices`, only select `SoCs` > `Zynq-7000`. - -13. Ensure Vivado and SDK are both selected (they are by default) and click `Next >`. - -14. *Recommended but optional:* Select "All users" for shortcut and file assocations. - -15. Review the install locations and download sizes (keep the defaults). Ensure you have space and click `Next >`. - -16. It will prompt that `C:\Xilinx` does not exist. Click `Yes` to create the folder. - -17. Click `Install`. It will now download the needed files and install them. This takes ~20-30 mins (depends on your internet speed)... - -18. At some point, the installer will prompt you to install "WinPcap". Do this with the defaults. - -19. At some point, the installer will prompt you to set-up MATLAB for the System Generator. Do this. It can be changed later. - -20. Finally, the installation will be complete. - -### License - -21. The Vivado License Manager will appear. Close this window without doing anything. Vivado seems to work without setting up the licensing. We will update this later if we learn more about this. - -*Vivado is now installed!* diff --git a/docs/Low-Level-Debugging.md b/docs/Low-Level-Debugging.md deleted file mode 100644 index 77fe643b..00000000 --- a/docs/Low-Level-Debugging.md +++ /dev/null @@ -1,58 +0,0 @@ -# Low-Level Debugging - -This document outlines various helpful commands for debugging the firmware. These will mostly use the Xilinx System Debugger (`xsdb`). -This is a command line tool which interacts with the hardware. One can program the FPGA, read memory and registers, etc. - -See the [official Xilinx documentation](https://www.xilinx.com/support/documentation/sw_manuals/xilinx2014_3/SDK_Doc/concepts/sdk_c_xsd_xsdb_commands.htm) for more `xsdb` commands. Also, see this [helpful guide](https://github.com/imrickysu/ZYNQ-Custom-Board-Bring-Up-Guide) for debugging Zynq-based projects. - - -## Open `xsdb` prompt - -1. Run `C:\Xilinx\Vivado\2017.2\bin\xsdb.bat` - -## Connect to AMDC via `xsdb` - -1. Ensure AMDC JTAG is plugged into PC -2. Ensure AMDC is powered on and in a reset state -3. `connect` -- should print `tcfchan#0` if connected -4. `targets` -- should print list of targets, 1-4 - -## Program AMDC through development environment - -1. Load the FPGA bitstream and DSP firmware - -## Read registers of running program - -In `xsdb`: - -1. `targets 2` -- connect to the ARM Cortex-A9 core that the code is running on -2. `rrd` -- prints registers with current values - -## Read registers of hardware peripherials - -In `xsdb`: - -1. `targets 1` -- connect to APU -2. `rrd` -- lists all possible peripherials to read from -3. Example: `rrd uart0` -- read UART0 registers - -## Read memory address - -Ensure code if up and running. In `xsdb`: - -1. `targets 2` -- connect to the running ARM Cortex-A9 core -2. `mrd ` -- read from hex global memory address - -## Determine why reboot occurs: - -Boot AMDC with your code prior to reboot. Make sure it is running fine. - -In `xsdb`: - -1. `targets 1` -- connect to API -2. `rrd slcr` -- read from slcr h/w -3. Look for `reboot_status` in left column. This encodes the reason for the last reboot (persisted through POR). Bits should be set to indicate JTAG debug reset... -4. `rwr slcr reboot_status 0x0` -- clear the `REBOOT_STATUS` register -5. Force the reboot to occur by breaking the firmware, etc -6. `rrd slcr` -- reread the `REBOOT_STATUS` register -7. Lookup bits that have been set: https://www.xilinx.com/support/answers/52030.html diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index b4537eab..00000000 --- a/docs/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Documentation - -Documentation has been written to help ease the process of using the AMDC platform. To get started, recommendation is given to read the docs in the order listed below. Once you understand the general [firmware architecture](Firmware-Architecture.md), try [downloading the code, building it, and running](Building-and-Running-Firmware.md) an example application (i.e., [`blink`](../sdk/bare/user/usr/blink/)) on the AMDC hardware. After your application is stable, try [flashing](Flashing-AMDC.md) it to the AMDC hardware for permanent usage. - -If you run into low-level issues (i.e. strange restart issues), consider using the Xilinx [debugging tools](Low-Level-Debugging.md) to investigate register state, etc. - -## [Firmware Architecture](Firmware-Architecture.md) -- [Drivers](Firmware-Arch-Drivers.md) -- [System](Firmware-Arch-System.md) -- [User Apps](Firmware-Arch-UserApps.md) - -## [GitHub to AMDC Hardware: Building and Running Firmware](Building-and-Running-Firmware.md) -- [Create Private Repo](Create-Private-Repo.md) - -## [Flashing AMDC](Flashing-AMDC.md) - -## [Low-Level Debugging](Low-Level-Debugging.md) - -## [Logging](Firmware-Logging.md) - -## [AMDC TestBoard Usage](TestBoard-Usage.md) - -## [Firmware Upgrades Per Hardware Target](Firmware-Upgrades-Per-Hardware-Target.md) diff --git a/docs/TestBoard-Usage.md b/docs/TestBoard-Usage.md deleted file mode 100644 index b065e59d..00000000 --- a/docs/TestBoard-Usage.md +++ /dev/null @@ -1,114 +0,0 @@ -# AMDC TestBoard Usage - -This document describes the AMDC TestBoard and the things for which it can be used. The necessary hardware setup is outlined and experimental waveforms are shown. Several practical use cases are outlined. - -Note that the TestBoard, and therefore this entire document, is only applicable to / supported by AMDC REV D hardware. - -## Hardware Setup - -To use the TestBoard, you will need to plug it into the AMDC. This requires four analog cables (like Ethernet cables) and one power stack cable (high density DB15 cable). - -If you need to buy cables, see some recommended options [here](https://github.com/Severson-Group/AMDC-Hardware/tree/develop/Accessories/TestBoard/REV20200624A#recommended-cables). - - - -#### AMDC Analog Port Connections - -The AMDC has a 2x2 RJ45 jack which the analog input voltages are read from. The TestBoard has the same jack. Use the four ethernet cables to connection these two jacks together. - -When looking at the jacks from the front, the locations match on the AMDC and TestBoard. For example, the top left port on the AMDC connects to the top left port on the TestBoard. - -#### AMDC PowerStack Port Connection - -The AMDC has eight PowerStack ports on the front, arranged in four stacks of two DB15 connectors. The TestBoard has a single DB15 connector. Plug in the DB15 cable into one of the eight AMDC PowerStack ports. Plug the other end into the TestBoard. - -Reminder: To enable the PWM outputs on the AMDC, the user must configure the AMDC `VDRIVE` voltage properly, as well as the E-STOP input. The appropriate `VDRIVE` voltage for the TestBoard is between 5V and 10V. **Do not exceed 10V on `VDRIVE` when using the TestBoard**. The AMDC PWM outputs are enabled if the E-STOP input is shorted. All PWM outputs go to the logic LOW state when the E-STOP is opened. - -#### TestBoard Configuration - -The TestBoard has a few jumpers which need to be configured. - -Ensure the DIP switches are in the OFF position (i.e. the switches are open). - -Ensure only one jumper (or no jumper) is installed across JP1 to JP6. This jumper connects the `VOUT` analog voltage to the BNC jack on the TestBoard. If you are not using the BNC jack, this jumper is optional. - -## Use Case #1: PCB Hardware Validation - -[See documentation about this here...](https://github.com/Severson-Group/AMDC-Firmware/tree/develop/sdk/bare/common/usr/pcbtest) - -## Use Case #2: TestBoard as DAC - -One of the features of the TestBoard is that it has RC filters attached to each of the six PWM outputs from the PowerStack port. These RC filters convert the digital PWM signals to analog voltages based on the duty ratios which the AMDC is outputting as well as the `VDRIVE` voltage level which the user has supplied the AMDC. - -For example, if `PWM1` is at 0% duty ratio, the analog voltage on `VOUT1` should be 0V. When `PWM1` is at 100% duty ratio, `VOUT1` should be equal to `VDRIVE`. However, due to PWM dead-time, a small offset must be subtracted. - -Therefore, the equation for VOUT is written as: - -*VOUT = (VDRIVE * pwm_duty_ratio) - small_offset_due_to_deadtime* - -From the above discussion, it should be obvious that the TestBoard can act as a simple digital to analog (DAC) device. The user can write the desired analog voltage value to the PWM duty ratio (as a percent of VDRIVE). That voltage will appear on the TestBoard. - -#### PWM Switching Effects - -Since VOUT is simply an RC filtered version of the raw PWM signal, the PWM switching parameters will heavily affect the analog voltage output, VOUT. The default values for the RC filters on the TestBoard are tuned to 10kHz bandwidth. - -As PWM switching frequency increases, the voltage ripple on VOUT will decrease. As PWM dead-time increases, the subtracted voltage offset will increase. - -The user can configure the PWM switching parameters on the AMDC using the `hw pwm sw ...` command. - -``` -# Set the PWM to 100kHz and 100ns of deadtime -hw pwm sw 100000 100 - -# Enable PWM output -hw pwm on - -# Disable PWM output -hw pwm off -``` - -The AMDC limits the PWM output frequency between 2kHz and 2MHz. It limits the minimum dead-time to 25ns. - -**When using the TestBoard as a DAC (and nothing else plugged into the inverter ports), configure the PWM switching parameters to 2MHz with 25ns of deadtime. This will result in the smoothest analog VOUT.** - -### Example Waveforms - -In the waveforms shown between, all six digital PWM testpoints (TP2 to TP7) on the TestBoard are measured as well as all six analog testpoints (TP16 to TP21). The waveforms are labeled on the left-hand side. - -VDRIVE is 5V. - -#### PWM Disabled - -![](images/testboard/no-pwm.png) - -#### PWM Enabled (50% Duty Ratio) - -50% duty ratio output on all channels at several PWM switching parameter points. - -##### Fsw = 2kHz, Tdt = 25ns - -![](images/testboard/pwm-enabled-2kHz-25ns-50d.png) - -##### Fsw = 100kHz, Tdt = 100ns - -![](images/testboard/pwm-enabled-100kHz-100ns-50d.png) - -##### Fsw = 2MHz, Tdt = 25ns - -![](images/testboard/pwm-enabled-2MHz-25ns-50d.png) - -#### Sinusoid Generation - -Firmware task updating PWM duty ratios to create three-phase sinusoidal output on TestBoard VOUT. - -##### Fsw = 2MHz, Tdt = 25ns - -_VOUT = cos(10*t)_ - -![](images/testboard/pwm-enabled-2MHz-25ns-sine-10rad.png) - -##### Fsw = 20kHz, Tdt = 25ns - -_VOUT = cos(1000*t)_ - -![](images/testboard/pwm-enabled-20kHz-25ns-sine-1000rad.png) diff --git a/docs/images/arch/block-diagram.svg b/docs/images/arch/block-diagram.svg deleted file mode 100644 index e41d4cc0..00000000 --- a/docs/images/arch/block-diagram.svg +++ /dev/null @@ -1,789 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - uart - Drivers - System - User Apps - - dac - - analog - - io - - pwm - - timer - - gpio - - encoder - - commands - - debug - - scheduler - - log - - serial - - - - - - - - - - - App 1 - App 2 - App 3 - H/W - - task - - cmd2 - - cmd1 - - task2 - - task1 - - cmd - - - - - - - - FPGA - ARM Cortex-A9 - ARM Cortex-A9 - - - - - timer2 - - - - timer1 - - - - dac - - - - encoder - - - - pwm - - - - analog - - - diff --git a/docs/images/arch/cmd-flow.svg b/docs/images/arch/cmd-flow.svg deleted file mode 100644 index f3ce8e10..00000000 --- a/docs/images/arch/cmd-flow.svg +++ /dev/null @@ -1,831 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - Host - Physical - AMDC - - Serial Terminal - foo bar baz - cmd arg arg2 - this is a cmd - so is this - blahblahblah - - - UART - - - - - - - - - - - - - - - - Incoming - f - o - o - " " - b - a - r - " " - b - a - z - - - - - - - - Pending - Commands - FIFO - FIFO - Character - foo bar baz - cmd1 - cmd2 - cmd3 - cmd4 - - - Command - Handler - argv: ["foo","bar","baz"] - argc: 3 - - - - - - - - - - - sys - usr - - diff --git a/docs/images/arch/tasks-example.svg b/docs/images/arch/tasks-example.svg deleted file mode 100644 index c2d1105e..00000000 --- a/docs/images/arch/tasks-example.svg +++ /dev/null @@ -1,476 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - T0 - T1 - T2 - T3 - T4 - T5 - T6 - - - - - - - - - Task 1: - - - - - - - - - - - - - - Idle: - - - - - - - - Task 2: - Task 3: - - - diff --git a/docs/images/flashing/img1.png b/docs/images/flashing/img1.png deleted file mode 100644 index 5d8e1ccf..00000000 Binary files a/docs/images/flashing/img1.png and /dev/null differ diff --git a/docs/images/flashing/img2.png b/docs/images/flashing/img2.png deleted file mode 100644 index 37f15be6..00000000 Binary files a/docs/images/flashing/img2.png and /dev/null differ diff --git a/docs/images/flashing/img3.png b/docs/images/flashing/img3.png deleted file mode 100644 index a2f257f1..00000000 Binary files a/docs/images/flashing/img3.png and /dev/null differ diff --git a/docs/images/flashing/img4.png b/docs/images/flashing/img4.png deleted file mode 100644 index 0bd8f77d..00000000 Binary files a/docs/images/flashing/img4.png and /dev/null differ diff --git a/docs/images/flashing/sw-flash.jpg b/docs/images/flashing/sw-flash.jpg deleted file mode 100644 index 9dbcfe40..00000000 Binary files a/docs/images/flashing/sw-flash.jpg and /dev/null differ diff --git a/docs/images/flashing/sw-jtag.jpg b/docs/images/flashing/sw-jtag.jpg deleted file mode 100644 index a9dc4d36..00000000 Binary files a/docs/images/flashing/sw-jtag.jpg and /dev/null differ diff --git a/docs/images/logging/terminal.png b/docs/images/logging/terminal.png deleted file mode 100644 index 9d5ad403..00000000 Binary files a/docs/images/logging/terminal.png and /dev/null differ diff --git a/docs/images/testboard/no-pwm.png b/docs/images/testboard/no-pwm.png deleted file mode 100644 index 0d7b1f54..00000000 Binary files a/docs/images/testboard/no-pwm.png and /dev/null differ diff --git a/docs/images/testboard/pwm-enabled-100kHz-100ns-50d.png b/docs/images/testboard/pwm-enabled-100kHz-100ns-50d.png deleted file mode 100644 index d5cfcda4..00000000 Binary files a/docs/images/testboard/pwm-enabled-100kHz-100ns-50d.png and /dev/null differ diff --git a/docs/images/testboard/pwm-enabled-20kHz-25ns-sine-1000rad.png b/docs/images/testboard/pwm-enabled-20kHz-25ns-sine-1000rad.png deleted file mode 100644 index 6574426e..00000000 Binary files a/docs/images/testboard/pwm-enabled-20kHz-25ns-sine-1000rad.png and /dev/null differ diff --git a/docs/images/testboard/pwm-enabled-2MHz-25ns-50d.png b/docs/images/testboard/pwm-enabled-2MHz-25ns-50d.png deleted file mode 100644 index 4a728bde..00000000 Binary files a/docs/images/testboard/pwm-enabled-2MHz-25ns-50d.png and /dev/null differ diff --git a/docs/images/testboard/pwm-enabled-2MHz-25ns-sine-10rad.png b/docs/images/testboard/pwm-enabled-2MHz-25ns-sine-10rad.png deleted file mode 100644 index a886ed6d..00000000 Binary files a/docs/images/testboard/pwm-enabled-2MHz-25ns-sine-10rad.png and /dev/null differ diff --git a/docs/images/testboard/pwm-enabled-2kHz-25ns-50d.png b/docs/images/testboard/pwm-enabled-2kHz-25ns-50d.png deleted file mode 100644 index 71d39735..00000000 Binary files a/docs/images/testboard/pwm-enabled-2kHz-25ns-50d.png and /dev/null differ diff --git a/docs/images/testboard/setup.jpg b/docs/images/testboard/setup.jpg deleted file mode 100644 index 31c1966d..00000000 Binary files a/docs/images/testboard/setup.jpg and /dev/null differ diff --git a/scripts/AMDC_Logger.py b/scripts/AMDC_Logger.py index 729d9083..001b0ff7 100644 --- a/scripts/AMDC_Logger.py +++ b/scripts/AMDC_Logger.py @@ -7,6 +7,7 @@ import datetime import struct import binascii +import re ######################################################### # Title: AMDC Logging code @@ -117,7 +118,7 @@ def unregister_all(self): for var in variables: self.unregister(var) - def clear(self, log_vars): + def empty(self, log_vars): variables = self._sanitize_inputs(log_vars) @@ -128,13 +129,13 @@ def clear(self, log_vars): except: pass - def clear_all(self): + def empty_all(self): self.amdc.cmd(f'log empty_all') #for var in self.log_vars: - # self.clear(var) + # self.empty(var) - def auto_find_vars(self, root): + def auto_find_vars(self, root, regex=None): log_vars = [] log_types = [] @@ -148,9 +149,22 @@ def auto_find_vars(self, root): for var, tp in zip(lv, lt): log_vars.append(var) log_types.append(tp) - - return log_vars, log_types - + + if regex is None: + # Return all + return log_vars, log_types + else: + # Filter by user-provided regex + output_vars = [] + output_types = [] + + for i,n in enumerate(log_vars): + if re.search(regex, n) is not None: + output_vars.append(log_vars[i]) + output_types.append(log_types[i]) + + return output_vars, output_types + def start(self): self.amdc.cmd('log start') diff --git a/sdk/app_cpu1/common/drv/eddy_current_sensor.c b/sdk/app_cpu1/common/drv/eddy_current_sensor.c index c48a2f99..d6c25eef 100644 --- a/sdk/app_cpu1/common/drv/eddy_current_sensor.c +++ b/sdk/app_cpu1/common/drv/eddy_current_sensor.c @@ -1,39 +1,44 @@ #include "drv/eddy_current_sensor.h" +#include "usr/user_config.h" #include "xil_io.h" #include #include #include -#define EDDY_CURRENT_SENSOR_BASE_ADDR (0x43C80000) - void eddy_current_sensor_init(void) { printf("EDDY CURRENT SENSOR:\tInitializing...\n"); // Set sampling rate to 20kHz - eddy_current_sensor_set_sample_rate(20000); + eddy_current_sensor_set_sample_rate(EDDY_CURRENT_SENSOR_1_BASE_ADDR, 20000); + +#if USER_CONFIG_HARDWARE_TARGET == AMDC_REV_E + eddy_current_sensor_set_sample_rate(EDDY_CURRENT_SENSOR_2_BASE_ADDR, 20000); + eddy_current_sensor_set_sample_rate(EDDY_CURRENT_SENSOR_3_BASE_ADDR, 20000); + eddy_current_sensor_set_sample_rate(EDDY_CURRENT_SENSOR_4_BASE_ADDR, 20000); +#endif } -void eddy_current_sensor_enable(void) +void eddy_current_sensor_enable(uint32_t base_addr) { - Xil_Out32(EDDY_CURRENT_SENSOR_BASE_ADDR + (3 * sizeof(uint32_t)), 1); + Xil_Out32(base_addr + (3 * sizeof(uint32_t)), 1); } -void eddy_current_sensor_disable(void) +void eddy_current_sensor_disable(uint32_t base_addr) { - Xil_Out32(EDDY_CURRENT_SENSOR_BASE_ADDR + (3 * sizeof(uint32_t)), 0); + Xil_Out32(base_addr + (3 * sizeof(uint32_t)), 0); } -void eddy_current_sensor_set_sample_rate(double sample_rate) +void eddy_current_sensor_set_sample_rate(uint32_t base_addr, double sample_rate) { uint8_t divider = (uint8_t)(500000 / sample_rate); - eddy_current_sensor_set_divider(divider - 1); + eddy_current_sensor_set_divider(base_addr, divider - 1); } -void eddy_current_sensor_set_divider(uint8_t divider) +void eddy_current_sensor_set_divider(uint32_t base_addr, uint8_t divider) { - Xil_Out32(EDDY_CURRENT_SENSOR_BASE_ADDR + (2 * sizeof(uint32_t)), divider); + Xil_Out32(base_addr + (2 * sizeof(uint32_t)), divider); } static double bits_to_voltage(uint32_t data) @@ -57,16 +62,16 @@ static double bits_to_voltage(uint32_t data) return voltage; } -double eddy_current_sensor_read_x_voltage(void) +double eddy_current_sensor_read_x_voltage(uint32_t base_addr) { - uint32_t x_data = Xil_In32(EDDY_CURRENT_SENSOR_BASE_ADDR); + uint32_t x_data = Xil_In32(base_addr); return bits_to_voltage(x_data); } -double eddy_current_sensor_read_y_voltage(void) +double eddy_current_sensor_read_y_voltage(uint32_t base_addr) { - uint32_t y_data = Xil_In32(EDDY_CURRENT_SENSOR_BASE_ADDR + (1 * sizeof(uint32_t))); + uint32_t y_data = Xil_In32(base_addr + (1 * sizeof(uint32_t))); return bits_to_voltage(y_data); } diff --git a/sdk/app_cpu1/common/drv/eddy_current_sensor.h b/sdk/app_cpu1/common/drv/eddy_current_sensor.h index 18555885..e9386163 100644 --- a/sdk/app_cpu1/common/drv/eddy_current_sensor.h +++ b/sdk/app_cpu1/common/drv/eddy_current_sensor.h @@ -1,21 +1,35 @@ -#ifndef eddy_current_sensor_H -#define eddy_current_sensor_H +#ifndef EDDY_CURRENT_SENSOR_H +#define EDDY_CURRENT_SENSOR_H +#include "drv/hardware_targets.h" +#include "usr/user_config.h" #include #include -void eddy_current_sensor_init(void); +#include "xparameters.h" -void eddy_current_sensor_enable(void); +#if USER_CONFIG_HARDWARE_TARGET == AMDC_REV_D +#define EDDY_CURRENT_SENSOR_MAX_IP_CORES (XPAR_AMDC_EDDY_CURRENT_SENSOR_NUM_INSTANCES) +#define EDDY_CURRENT_SENSOR_1_BASE_ADDR (XPAR_AMDC_EDDY_CURRENT_SE_0_S00_AXI_BASEADDR) +#endif -void eddy_current_sensor_disable(void); +#if USER_CONFIG_HARDWARE_TARGET == AMDC_REV_E +#define EDDY_CURRENT_SENSOR_MAX_IP_CORES (XPAR_AMDC_EDDY_CURRENT_SENSOR_NUM_INSTANCES) +#define EDDY_CURRENT_SENSOR_1_BASE_ADDR (XPAR_HIER_GPIO_0_AMDC_EDDY_CURRENT_SE_0_S00_AXI_BASEADDR) +#define EDDY_CURRENT_SENSOR_2_BASE_ADDR (XPAR_HIER_GPIO_1_AMDC_EDDY_CURRENT_SE_0_S00_AXI_BASEADDR) +#define EDDY_CURRENT_SENSOR_3_BASE_ADDR (XPAR_HIER_GPIO_2_AMDC_EDDY_CURRENT_SE_0_S00_AXI_BASEADDR) +#define EDDY_CURRENT_SENSOR_4_BASE_ADDR (XPAR_HIER_GPIO_3_AMDC_EDDY_CURRENT_SE_0_S00_AXI_BASEADDR) +#endif -void eddy_current_sensor_set_sample_rate(double); +void eddy_current_sensor_init(void); -void eddy_current_sensor_set_divider(uint8_t); +void eddy_current_sensor_enable(uint32_t base_addr); +void eddy_current_sensor_disable(uint32_t base_addr); -double eddy_current_sensor_read_x_voltage(void); +void eddy_current_sensor_set_sample_rate(uint32_t base_addr, double sample_rate); +void eddy_current_sensor_set_divider(uint32_t base_addr, uint8_t divider); -double eddy_current_sensor_read_y_voltage(void); +double eddy_current_sensor_read_x_voltage(uint32_t base_addr); +double eddy_current_sensor_read_y_voltage(uint32_t base_addr); #endif // EDDY_CURRENT_SENSOR_H diff --git a/sdk/app_cpu1/common/drv/fpga_timer.c b/sdk/app_cpu1/common/drv/fpga_timer.c index cc8f247e..cbb94080 100644 --- a/sdk/app_cpu1/common/drv/fpga_timer.c +++ b/sdk/app_cpu1/common/drv/fpga_timer.c @@ -1,11 +1,17 @@ #include "drv/fpga_timer.h" #include "sys/defines.h" +#include "usr/user_config.h" #include "xparameters.h" #include "xtmrctr.h" #include -// Get the TIMER_1 device from the FPGA +#if USER_CONFIG_HARDWARE_TARGET == AMDC_REV_D +#define TMR_DEVICE_ID XPAR_CONTROL_TIMER_1_DEVICE_ID +#endif + +#if USER_CONFIG_HARDWARE_TARGET == AMDC_REV_E #define TMR_DEVICE_ID XPAR_HIER_TIMERS_CONTROL_TIMER_1_DEVICE_ID +#endif static XTmrCtr timer; diff --git a/sdk/app_cpu1/common/drv/motherboard.c b/sdk/app_cpu1/common/drv/motherboard.c index 88f66afa..c0d7b05c 100644 --- a/sdk/app_cpu1/common/drv/motherboard.c +++ b/sdk/app_cpu1/common/drv/motherboard.c @@ -5,30 +5,35 @@ #include "drv/motherboard.h" #include "drv/motherboard_defs.h" #include "sys/cmd/cmd_mb.h" -#include "sys/debug.h" +#include "sys/commands.h" #include "sys/defines.h" #include "sys/util.h" #include #include -static volatile uint32_t *m_motherboard; - -void motherboard_init(uint32_t base_addr) +void motherboard_init(void) { - // Store base address for IP - m_motherboard = (volatile uint32_t *) base_addr; - cmd_mb_register(); #if USER_CONFIG_ENABLE_MOTHERBOARD_AUTO_TX == 1 - motherboard_set_adc_sampling(true); + bool auto_tx = true; #else - motherboard_set_adc_sampling(false); + bool auto_tx = false +#endif + + motherboard_set_adc_sampling(MOTHERBOARD_1_BASE_ADDR, auto_tx); +#if USER_CONFIG_HARDWARE_TARGET == AMDC_REV_E + motherboard_set_adc_sampling(MOTHERBOARD_2_BASE_ADDR, auto_tx); + motherboard_set_adc_sampling(MOTHERBOARD_3_BASE_ADDR, auto_tx); + motherboard_set_adc_sampling(MOTHERBOARD_4_BASE_ADDR, auto_tx); #endif } -void motherboard_set_adc_sampling(bool enable) +void motherboard_set_adc_sampling(uint32_t base_addr, bool enable) { + // Create base address for IP + volatile uint32_t *m_motherboard = (volatile uint32_t *) base_addr; + // Read register from FPGA uint32_t reg = m_motherboard[MOTHERBOARD_DEFS_OFFSET_CONTROL / 4]; @@ -44,8 +49,11 @@ void motherboard_set_adc_sampling(bool enable) m_motherboard[MOTHERBOARD_DEFS_OFFSET_CONTROL / 4] = reg; } -void motherboard_request_new_data(void) +void motherboard_request_new_data(uint32_t base_addr) { + // Create base address for IP + volatile uint32_t *m_motherboard = (volatile uint32_t *) base_addr; + // Read register from FPGA uint32_t reg = m_motherboard[MOTHERBOARD_DEFS_OFFSET_CONTROL / 4]; @@ -56,8 +64,11 @@ void motherboard_request_new_data(void) m_motherboard[MOTHERBOARD_DEFS_OFFSET_CONTROL / 4] = reg; } -int motherboard_get_data(mb_channel_e channel, int32_t *out) +int motherboard_get_data(uint32_t base_addr, mb_channel_e channel, int32_t *out) { + // Create base address for IP + volatile uint32_t *m_motherboard = (volatile uint32_t *) base_addr; + if (!motherboard_is_valid_channel(channel)) { return FAILURE; } else { @@ -66,19 +77,25 @@ int motherboard_get_data(mb_channel_e channel, int32_t *out) } } -void motherboard_print_samples(void) +void motherboard_print_samples(uint32_t base_addr) { + // Create base address for IP + volatile uint32_t *m_motherboard = (volatile uint32_t *) base_addr; + for (int i = 0; i < 8; i++) { uint32_t val = m_motherboard[i]; - debug_printf("%i: %04X\r\n", i, val); + cmd_resp_printf("%i: %04X\r\n", i, val); } } -void motherboard_print_counters(void) +void motherboard_print_counters(uint32_t base_addr) { - debug_printf("V: %08X\r\n", m_motherboard[MOTHERBOARD_DEFS_OFFSET_COUNT_VALID / 4]); - debug_printf("C: %08X\r\n", m_motherboard[MOTHERBOARD_DEFS_OFFSET_COUNT_CORRUPT / 4]); - debug_printf("T: %08X\r\n", m_motherboard[MOTHERBOARD_DEFS_OFFSET_COUNT_TIMEOUT / 4]); + // Create base address for IP + volatile uint32_t *m_motherboard = (volatile uint32_t *) base_addr; + + cmd_resp_printf("V: %08X\r\n", m_motherboard[MOTHERBOARD_DEFS_OFFSET_COUNT_VALID / 4]); + cmd_resp_printf("C: %08X\r\n", m_motherboard[MOTHERBOARD_DEFS_OFFSET_COUNT_CORRUPT / 4]); + cmd_resp_printf("T: %08X\r\n", m_motherboard[MOTHERBOARD_DEFS_OFFSET_COUNT_TIMEOUT / 4]); } #endif // USER_CONFIG_ENABLE_MOTHERBOARD_SUPPORT diff --git a/sdk/app_cpu1/common/drv/motherboard.h b/sdk/app_cpu1/common/drv/motherboard.h index 2afbd764..b710cc66 100644 --- a/sdk/app_cpu1/common/drv/motherboard.h +++ b/sdk/app_cpu1/common/drv/motherboard.h @@ -1,10 +1,25 @@ #ifndef MOTHERBOARD_H #define MOTHERBOARD_H +#include "drv/hardware_targets.h" +#include "usr/user_config.h" #include #include -#define MOTHERBOARD_BASE_ADDR (0x43C90000) +#include "xparameters.h" + +#if USER_CONFIG_HARDWARE_TARGET == AMDC_REV_D +#define MOTHERBOARD_MAX_IP_CORES (XPAR_AMDC_MOTHERBOARD_NUM_INSTANCES) +#define MOTHERBOARD_1_BASE_ADDR (XPAR_HIER_AMDS_AMDC_MOTHERBOARD_0_S00_AXI_BASEADDR) +#endif + +#if USER_CONFIG_HARDWARE_TARGET == AMDC_REV_E +#define MOTHERBOARD_MAX_IP_CORES (XPAR_AMDC_MOTHERBOARD_NUM_INSTANCES) +#define MOTHERBOARD_1_BASE_ADDR (XPAR_HIER_GPIO_0_HIER_AMDS_0_AMDC_MOTHERBOARD_0_S00_AXI_BASEADDR) +#define MOTHERBOARD_2_BASE_ADDR (XPAR_HIER_GPIO_1_HIER_AMDS_0_AMDC_MOTHERBOARD_0_S00_AXI_BASEADDR) +#define MOTHERBOARD_3_BASE_ADDR (XPAR_HIER_GPIO_2_HIER_AMDS_0_AMDC_MOTHERBOARD_0_S00_AXI_BASEADDR) +#define MOTHERBOARD_4_BASE_ADDR (XPAR_HIER_GPIO_3_HIER_AMDS_0_AMDC_MOTHERBOARD_0_S00_AXI_BASEADDR) +#endif typedef enum { // Keep first channel index at 0! @@ -30,14 +45,43 @@ static inline bool motherboard_is_valid_channel(mb_channel_e channel) return false; } -void motherboard_init(uint32_t base_addr); +static inline bool motherboard_is_valid_idx(int idx) +{ + if (idx >= 0 && idx < MOTHERBOARD_MAX_IP_CORES) { + return true; + } + + return false; +} + +static inline uint32_t motherboard_idx_to_base_addr(int idx) +{ + switch (idx) { + case 0: + return MOTHERBOARD_1_BASE_ADDR; + +#if USER_CONFIG_HARDWARE_TARGET == AMDC_REV_E + case 1: + return MOTHERBOARD_2_BASE_ADDR; + case 2: + return MOTHERBOARD_3_BASE_ADDR; + case 3: + return MOTHERBOARD_4_BASE_ADDR; +#endif + + default: + return 0; + } +} + +void motherboard_init(void); -void motherboard_set_adc_sampling(bool enable); -void motherboard_request_new_data(void); +void motherboard_set_adc_sampling(uint32_t base_addr, bool enable); +void motherboard_request_new_data(uint32_t base_addr); -int motherboard_get_data(mb_channel_e channel, int32_t *out); +int motherboard_get_data(uint32_t base_addr, mb_channel_e channel, int32_t *out); -void motherboard_print_samples(void); -void motherboard_print_counters(void); +void motherboard_print_samples(uint32_t base_addr); +void motherboard_print_counters(uint32_t base_addr); #endif // MOTHERBOARD_H diff --git a/sdk/app_cpu1/common/drv/pwm.c b/sdk/app_cpu1/common/drv/pwm.c index 40ce40f3..ca5326fb 100644 --- a/sdk/app_cpu1/common/drv/pwm.c +++ b/sdk/app_cpu1/common/drv/pwm.c @@ -113,14 +113,34 @@ void pwm_set_all_rst(uint8_t rst) Xil_Out32(PWM_BASE_ADDR + (27 * sizeof(uint32_t)), value); } +// Hardware disabling of PWM was added to REV E hardware +#if USER_CONFIG_HARDWARE_TARGET == AMDC_REV_E + +static bool is_pwm_enable_hw_enabled = false; + int pwm_enable_hw(bool en) { - if (en) { + int err = FAILURE; + if (en) { + if (!is_pwm_enable_hw_enabled) { + XGpioPs_WritePin(&Gpio, pin_PS_DRIVE_EN_MIO, 1); + is_pwm_enable_hw_enabled = true; + err = SUCCESS; + } } else { + if (is_pwm_enable_hw_enabled) { + XGpioPs_WritePin(&Gpio, pin_PS_DRIVE_EN_MIO, 0); + is_pwm_enable_hw_enabled = false; + err = SUCCESS; + } } + + return err; } +#endif + int pwm_enable(void) { if (pwm_is_enabled()) { diff --git a/sdk/app_cpu1/common/drv/pwm.h b/sdk/app_cpu1/common/drv/pwm.h index 105c988f..8ed81c87 100644 --- a/sdk/app_cpu1/common/drv/pwm.h +++ b/sdk/app_cpu1/common/drv/pwm.h @@ -60,7 +60,9 @@ void pwm_init(void); void pwm_toggle_reset(void); void pwm_set_all_rst(uint8_t rst); +#if USER_CONFIG_HARDWARE_TARGET == AMDC_REV_E int pwm_enable_hw(bool en); +#endif int pwm_enable(void); int pwm_disable(void); diff --git a/sdk/app_cpu1/common/main.c b/sdk/app_cpu1/common/main.c index 4523b29f..5a8aa1a0 100644 --- a/sdk/app_cpu1/common/main.c +++ b/sdk/app_cpu1/common/main.c @@ -38,6 +38,18 @@ #include "xil_mmu.h" #include "xpseudo_asm.h" +#include "xparameters.h" + +// REV E has 4 GPIO ports, each with their own eddy current sensor driver IP block: +#if (USER_CONFIG_HARDWARE_TARGET == AMDC_REV_E) && (XPAR_AMDC_EDDY_CURRENT_SENSOR_NUM_INSTANCES != 4) +#error "ERROR: Vivado hardware target is REV D, but usr/user_config.h target is REV E!" +#endif + +// REV D has 2 GPIO ports, each with their own eddy current sensor driver IP block: +#if (USER_CONFIG_HARDWARE_TARGET == AMDC_REV_D) && (XPAR_AMDC_EDDY_CURRENT_SENSOR_NUM_INSTANCES != 2) +#error "ERROR: Vivado hardware target is REV E, but usr/user_config.h target is REV D!" +#endif + int main() { // Required system initialization @@ -64,7 +76,7 @@ int main() #if USER_CONFIG_ENABLE_MOTHERBOARD_SUPPORT == 1 // Initialize motherboard driver and register command - motherboard_init(MOTHERBOARD_BASE_ADDR); + motherboard_init(); #endif // Register the "cnt" command diff --git a/sdk/app_cpu1/common/sys/cmd/cmd_hw.c b/sdk/app_cpu1/common/sys/cmd/cmd_hw.c index f1255d26..23000eb9 100644 --- a/sdk/app_cpu1/common/sys/cmd/cmd_hw.c +++ b/sdk/app_cpu1/common/sys/cmd/cmd_hw.c @@ -50,6 +50,12 @@ int cmd_hw(int argc, char **argv) // Handle 'pwm' sub-command if (argc >= 2 && STREQ("pwm", argv[1])) { if (argc == 3 && STREQ("on", argv[2])) { +#if USER_CONFIG_HARDWARE_TARGET == AMDC_REV_E + // Turn on PWM hardware before enabling + // so that the first cycle has correct duty + pwm_enable_hw(true); +#endif + if (pwm_enable() != SUCCESS) { return CMD_FAILURE; } @@ -62,6 +68,12 @@ int cmd_hw(int argc, char **argv) return CMD_FAILURE; } +#if USER_CONFIG_HARDWARE_TARGET == AMDC_REV_E + // Turn off PWM hardware after disabling + // so that the last cycle has correct duty + pwm_enable_hw(false); +#endif + return CMD_SUCCESS; } diff --git a/sdk/app_cpu1/common/sys/cmd/cmd_mb.c b/sdk/app_cpu1/common/sys/cmd/cmd_mb.c index b6927399..0a62d2b1 100644 --- a/sdk/app_cpu1/common/sys/cmd/cmd_mb.c +++ b/sdk/app_cpu1/common/sys/cmd/cmd_mb.c @@ -13,10 +13,10 @@ static command_entry_t cmd_entry; static command_help_t cmd_help[] = { - { "adc ", "Controls motherboard ADC sampling" }, - { "tx", "Manually trigger motherboard to send latest ADC samples" }, - { "samples", "Display latest samples from MB" }, - { "counters", "Display debug counters from MB UART RX" }, + { " adc ", "Controls motherboard ADC sampling" }, + { " tx", "Manually trigger motherboard to send latest ADC samples" }, + { " samples", "Display latest samples from MB" }, + { " counters", "Display debug counters from MB UART RX" }, }; void cmd_mb_register(void) @@ -28,34 +28,44 @@ void cmd_mb_register(void) int cmd_mb(int argc, char **argv) { - // Handle 'mb adc ' command - if (argc == 3 && STREQ("adc", argv[1])) { - if (STREQ("on", argv[2])) { - motherboard_set_adc_sampling(true); + uint32_t base_addr = 0; + if (argc >= 2) { + int idx = atoi(argv[1]); + if (!motherboard_is_valid_idx(idx)) { + return CMD_INVALID_ARGUMENTS; + } + + base_addr = motherboard_idx_to_base_addr(idx); + } + + // Handle 'mb adc ' command + if (argc == 4 && STREQ("adc", argv[2])) { + if (STREQ("on", argv[3])) { + motherboard_set_adc_sampling(base_addr, true); return CMD_SUCCESS; } - if (STREQ("off", argv[2])) { - motherboard_set_adc_sampling(false); + if (STREQ("off", argv[3])) { + motherboard_set_adc_sampling(base_addr, false); return CMD_SUCCESS; } } - // Handle 'mb tx' command - if (argc == 2 && STREQ("tx", argv[1])) { - motherboard_request_new_data(); + // Handle 'mb tx' command + if (argc == 3 && STREQ("tx", argv[2])) { + motherboard_request_new_data(base_addr); return CMD_SUCCESS; } - // Handle 'mb samples' command - if (argc == 2 && STREQ("samples", argv[1])) { - motherboard_print_samples(); + // Handle 'mb samples' command + if (argc == 3 && STREQ("samples", argv[2])) { + motherboard_print_samples(base_addr); return CMD_SUCCESS; } - // Handle 'mb counters' command - if (argc == 2 && STREQ("counters", argv[1])) { - motherboard_print_counters(); + // Handle 'mb counters' command + if (argc == 3 && STREQ("counters", argv[2])) { + motherboard_print_counters(base_addr); return CMD_SUCCESS; } diff --git a/sdk/app_cpu1/common/sys/injection.c b/sdk/app_cpu1/common/sys/injection.c index fb8c2572..317501b9 100644 --- a/sdk/app_cpu1/common/sys/injection.c +++ b/sdk/app_cpu1/common/sys/injection.c @@ -382,7 +382,7 @@ void state_machine_list_callback(void *arg) switch (ctx->state) { case LISTING: // Print entry - debug_printf("%s\r\n", ctx->curr->name); + cmd_resp_printf("%s\r\n", ctx->curr->name); // Move to next entry ctx->curr = ctx->curr->next; diff --git a/sdk/app_cpu1/common/sys/log.h b/sdk/app_cpu1/common/sys/log.h index 406c5850..ef128d13 100644 --- a/sdk/app_cpu1/common/sys/log.h +++ b/sdk/app_cpu1/common/sys/log.h @@ -74,5 +74,6 @@ int log_var_is_registered(int idx, bool *is_registered); int log_print_info(void); int log_stream(bool enable, int idx, int socket_id); +void log_stream_synctime(void); #endif // LOG_H diff --git a/sdk/app_cpu1/common/sys/scheduler.c b/sdk/app_cpu1/common/sys/scheduler.c index d156cff5..5f8bde3e 100644 --- a/sdk/app_cpu1/common/sys/scheduler.c +++ b/sdk/app_cpu1/common/sys/scheduler.c @@ -188,7 +188,10 @@ void scheduler_run(void) // // NOTE: this is specifically before the while loop below so that the new // data arrives before it is needed in the next control loop. - motherboard_request_new_data(); + motherboard_request_new_data(MOTHERBOARD_1_BASE_ADDR); + motherboard_request_new_data(MOTHERBOARD_2_BASE_ADDR); + motherboard_request_new_data(MOTHERBOARD_3_BASE_ADDR); + motherboard_request_new_data(MOTHERBOARD_4_BASE_ADDR); #endif // Wait here until unpaused (i.e. when SysTick fires) diff --git a/sdk/app_cpu1/common/sys/task_stats.c b/sdk/app_cpu1/common/sys/task_stats.c index 7fbc8fee..9b8ddc53 100644 --- a/sdk/app_cpu1/common/sys/task_stats.c +++ b/sdk/app_cpu1/common/sys/task_stats.c @@ -1,6 +1,6 @@ #include "sys/task_stats.h" #include "drv/fpga_timer.h" -#include "sys/debug.h" +#include "sys/commands.h" #include "sys/scheduler.h" #include #include @@ -91,66 +91,67 @@ static void state_machine_callback(void *arg) switch (ctx->state) { case PRINT_HEADER: - debug_printf("Task Stats:\r\n"); + cmd_resp_printf("Task Stats:\r\n"); ctx->state = PRINT_LOOP_NUM_SAMPLES; break; // Loop timings // ... case PRINT_LOOP_NUM_SAMPLES: - debug_printf("Loop Num:\t%d samples\r\n", ctx->stats->loop_time.num_samples); + cmd_resp_printf("Loop Num:\t%d samples\r\n", ctx->stats->loop_time.num_samples); ctx->state = PRINT_LOOP_MIN; break; case PRINT_LOOP_MIN: - debug_printf("Loop Min:\t%.2f usec\r\n", ctx->stats->loop_time.min); + cmd_resp_printf("Loop Min:\t%.2f usec\r\n", ctx->stats->loop_time.min); ctx->state = PRINT_LOOP_MAX; break; case PRINT_LOOP_MAX: - debug_printf("Loop Max:\t%.2f usec\r\n", ctx->stats->loop_time.max); + cmd_resp_printf("Loop Max:\t%.2f usec\r\n", ctx->stats->loop_time.max); ctx->state = PRINT_LOOP_MEAN; break; case PRINT_LOOP_MEAN: - debug_printf("Loop Mean:\t%.2f usec\r\n", ctx->stats->loop_time.mean); + cmd_resp_printf("Loop Mean:\t%.2f usec\r\n", ctx->stats->loop_time.mean); ctx->state = PRINT_LOOP_VARIANCE; break; case PRINT_LOOP_VARIANCE: - debug_printf("Loop Var:\t%.2f usec\r\n", statistics_variance(&ctx->stats->loop_time)); + cmd_resp_printf("Loop Var:\t%.2f usec\r\n", statistics_variance(&ctx->stats->loop_time)); ctx->state = PRINT_RUN_NUM_SAMPLES; break; // Run timings // ... case PRINT_RUN_NUM_SAMPLES: - debug_printf("Run Num:\t%d samples\r\n", ctx->stats->run_time.num_samples); + cmd_resp_printf("Run Num:\t%d samples\r\n", ctx->stats->run_time.num_samples); ctx->state = PRINT_RUN_MIN; break; case PRINT_RUN_MIN: - debug_printf("Run Min:\t%.2f usec\r\n", ctx->stats->run_time.min); + cmd_resp_printf("Run Min:\t%.2f usec\r\n", ctx->stats->run_time.min); ctx->state = PRINT_RUN_MAX; break; case PRINT_RUN_MAX: - debug_printf("Run Max:\t%.2f usec\r\n", ctx->stats->run_time.max); + cmd_resp_printf("Run Max:\t%.2f usec\r\n", ctx->stats->run_time.max); ctx->state = PRINT_RUN_MEAN; break; case PRINT_RUN_MEAN: - debug_printf("Run Mean:\t%.2f usec\r\n", ctx->stats->run_time.mean); + cmd_resp_printf("Run Mean:\t%.2f usec\r\n", ctx->stats->run_time.mean); ctx->state = PRINT_RUN_VARIANCE; break; case PRINT_RUN_VARIANCE: - debug_printf("Run Var:\t%.2f usec\r\n", statistics_variance(&ctx->stats->run_time)); + cmd_resp_printf("Run Var:\t%.2f usec\r\n", statistics_variance(&ctx->stats->run_time)); ctx->state = REMOVE_TASK; break; case REMOVE_TASK: - debug_printf("\r\n"); + cmd_resp_printf("\r\n"); + cmd_resp_printf("SUCCESS\r\n\n"); scheduler_tcb_unregister(&ctx->tcb); break;