The goal of this tutorial is to build a simple two-wheeled motorized robot controlled by a Zedboard. You will use the Connectal framework to implement control logic consisting of both SW and HW (FPGA) components. Through this process, we hope you will gain an understanding of how HW/SW communication can be implemented using Connectal Portals. More details about the framework are available in the developers guide.
Step 1: Parts List
This project takes a Motor Robot Kit (MRK Basic) from Digilent Inc. and implements the control using a Zedboard instead of the Cerebot MX4cK which is included in the kit. If you don't want a spare MX4cK board, you might consider purchasing the all the components of the kit excluding the MX4cK individually.
Step 2: Attach the Motor Mount to the Platform
The Zedboard is slightly too large for the MRK platform. To accommodate it's size, you will need to attach the motor mount at an offset as shown in the images. This effectively increases the surface area of the platform, permitting a more secure connection to the Zedboard.
Step 3: Attach Pmod Clips and Drag Button to Underside of Platform
For the reminder of this tutorial, the Front of the board will refer to the side to which the drag button is attached, while the Rear will refer to the side where the motor mount is attached. Right and Left sides are designated from a forward-facing perspective when the robot is sitting on it's wheels (as one would with a car).
Step 4: Attach Zedboard to Top of Platform
Because of the alignment of the holes in the extended platform, only three of the four posts on the Zedboard will align with holes in the platform. Make sure that the FMC/SDCard end of the Zedboard sits on top of the motor mount.
Step 5: Attach the Motors to the Motor Mount
Step 6: Attach Battery Pack and HBridge Modules to the Bottom of the Platform
The motors will draw power from the battery pack. Using the wires from the MRK, connect both HBridge modules to the battery pack in parallel as shown.
Step 7: Connect HBridges to the Zedboard
Use two 6-pin cables to connect the two HBridge modules to the Zedboard. Connect the HBridge controlling the Left motor to the bottom row of pins in PMod JC1, making sure that pin1 on the HBridge Module connects to JC3_P. Connect the HBridge controlling the Right motor to the top row of pins in PMod JC1, making sure that pin1 on the HBridge Module connects to JC1_P.
Step 8: Configure Your Zedboard and Drive the Robot
If you haven't already done so, follow the Getting Started tutorial to connect your Zedboard and boot Linux.
Now you will use the Connectal framework to compile the HW and SW components of the controller, program the board, and execute some commands to drive the robot. A reference project is is hosted on github. This tutorial instructs users on how to compile a Connectal project using our publicly available build server. When you build the reference project, the repo should be entered as "git://github.com/cambridgehackers/connectal.git" and the path should be "examples/hbridge_simple". Make sure you specify the build target to be 'zedboard' and not 'bluesim'.
This tutorial instructs users on how to download a compiled project to a Zedboard and execute it. Before doing so, make sure that your machine has some room to roam.
Step 9: Understanding the Project Source
The remainder of this tutorial is spent looking at the source and underlying structure of the Connectal project which is used to control the robot. At this point you will probably want to copy the reference project to your own github repository so that you can modify source files and trigger re-builds. The buildbot running at connectalbuild.qrclab.com will detect when you have committed changes to project repositories and automatically check out the updates and attempt to build them.
If you are unwilling to wait the minute or so required for the automatic detection, you can force a build manually. To do this, select the project (from the Waterfall Display, for example) and scroll to the bottom of the page. You can specify a branch/revision to build, or just leave the fields blank in which case it will default to master.
Step 10: Makefile
CONNECTALDIR: If are compiling on your own machine, use this variable to point to the location of the Connectal source tree which you will build against. If you are using the build server, leave this unchanged since it is defined as an environment variable by Buildbot.
INTERFACES: This list contains the names of the bsv interfaces which may be used as Portals. SW will send commands to the HW controller through the 'HBridgeCtrlRequest' interface, and HW will report asynchronous events to SW through the 'HBridgeCtrlIndication' interface.
BSVFILES: This lists the bsv files specific to this project. Controller.bsv implements the control logic and Top.bsv connects all the pieces together.
CPPFILES: This lists the cpp files specific to this project. The SW components of the controller are implemented in a single file named test_hbridge.cpp.
NUMBER_OF_MASTERS: If the hardware access host memory directly, this is set to reflect the number of bus-mastering interfaces it exports.
PIN_TYPE: The HW component of a default Connectal project interacts with the outside world through the system bus exclusively (AXI or PCIe). If your design interacts with other hardware peripherals, you will need to specify a supplementary BSV interface through which this interaction will take place. In this project, we have defined a BSV interface called HBridge2 through which the FPGA logic can set the control registers of the two hbridge devices.
CONNECTALFLAGS, PIN_BINDING, gentarget: these three makefile variables are used to connect the supplemental wires to the pins on the FPGA device (which in turn connect to the PMod wires on the zedboard). We will discuss them at greater length when examining how to specify the pinout for the project.
Step 11: Controller.bsv
This file contains the core interface definitions and HW functionality of the project.
HBridgeCtrlRequest/HBridgeCtrlIndication provide interfaces for controlling the HBridge (from SW) and reporting asynchronous events (from HW).
HBridge2 defines an interface through which the hbridge control registers can be set. Because we are only writing the registers, we export an interface composed exclusive of value methods. Bsc compiles these value methods into named signals (wires) which are connected to the physical pins.
The module mkController implements the core logic of our project. It exports the 'pins' interfaces which is used to drive the hbridge modules, and the 'req' interface which is invoked from SW through a portal. It's single ctor argument is an interface 'ind' by which the controller invokes SW functionality through a second portal. The logic is intentionally simple and should be easily understandable to even a beginning Bluespec programmer.
Step 12: Top.bsv
Top.bsv instantiates all internal state and defines the interfaces which the Connectal framework connects to the physical pins on the FPGA device.
In our design, the HBridgeCtrlIndication interface is implemented in SW and invoked from HW. As a result, we instantiate the generated proxy mkHBridgeCtrlIndicationProxy. This module has a sub-interface 'ifc' of type 'HbridgeCtrlIndication' which is passed to the module mkController to be invoked directly from it's internal logic. Because HBridgeCtrlRequest is implemented in HW and invoked from SW, we must wrap it using the generated module mKHBridgeCtrlRequestWrapper to connect it to the bus. Both the wrapper and proxy are connected to the system bus by first muxing them with 'mkSlaveMux' and connecting the resulting single interface to the 'slave' interface.
The bus slave and masters, as well as the interrupts and leds have been assigned dedicated interfaces. Connectal framework automatically connects these to the correct pins depending on the specified build target. Failure to define these interfaces will not result in a build error, but will likely result in run-time anomalies. For example, if the SW relies on the system call 'poll' to be notified of a pending message from HW, failure to define the interrupt interface will cause the design to hang. If you don't define the slave interface, the SW proxies will report a failure when they attempt to locate the correct register mapping at startup time.
Lastly, the package/file which defines the supplemental pins interface must be explicitly exported so that it can be used by the platform-specific top (mkZynqTop, mkBsimTop, etc.). The platform-specific top is selected by the tool-chain depending on the build target. These files are located in $CONNECTALDIR/bsv.
Step 13: Pinout.json
The directory $CONNECTALDIR/boardinfo contains a json file for each supported platform which describes the physical pins of the FPGA device. These pins are divided into groups which roughly correspond to the physical connectors on the board. zedboard.json, for example, contains a group "fmc1", which describes the pins in the FMC connector. It contains "pmoda", "pmodb", "pmodc", "pmodd", and "pmode" which specify the pins for each of the 5 pmod connectors. Finally, there is a group named "pins", which contains an arbitrary selection of peripherals we have used in our examples, such as leds and switches.
hbridge_simple exports a supplemental pins interface. We need to specify how to connect these signals to the physical pins. Failure to fully specify this pinout will result in a build failure (Vivado issues a warning during link phase, and fails in the final bitgen phase). We have specified the mapping in pinout.jason, which associates a name in the generated Verilog to a pin in the boardinfo file. Because many of the connectors are somewhat modular, we have found it useful to add one level of indirection between the group names used in the pinout file and group names defined on the boardinfo file. In pinout.json, you can see that we are using pins J1, J2, J5, and J6 in group "pmod". Because the group "pmod" doesn't exist in the board info file, we specify PIN_BINDING in the Makefile to indicate the actual PMOD connector we are plugged into. We could just as easily have specified "pmodc" directly in pinout.json, but by using this mapping we give ourselves greater flexibility if we ever want to connect the hbridges to a different pmod connector.
Finally, we append a dependency to the gentarget makefile target in the Makefile to generate the actual consraints file. The generated .xdc file must be added as a build constraint using CONNECTALFLAGS.
Step 14: Test_hbridge.cpp
This file defines the SW component of our project. Because the interface HBridgeCtrlIndication is being implemented in SW and invoked from HW, we need to implement it as an inherited class of the machine-generated HBridgeCtrlIndicationWrapper. The generated wrapper declares a pure virtual instance of every method in the original definition of the HBridgeCtrlIndication BSV interface, which forces the programmer to provide a concrete implementation for each one.
In main, we create an instance of our HBridgeCtrlIndication (Wrapper) and an instance of the generated proxy for the HBridgeCtrlRequest interface which has been implemented in HW. The generated ctors for each of these modules contains all the initialization code which locates the correct HW registers and maps them into user space. The ctor for wrappers also registers the object with the interrupt-drive event handler, which is initiated through a call to pthread_create by invoking portalExec_start().
After instantiating the wrappers and proxies, we are ready to start sending commands to the controller through the designated Portal. When the HW invokes SW functionality, an interrupt is raised which wakes the event thread and executes the specified method interface method. Communication between the main thread and the event handler is implemented using standard multi-threaded programming techniques.